refactor: extensions

This commit is contained in:
cha0s 2024-07-17 20:43:29 -05:00
parent 0d8cdff6d7
commit 82fd31802b
3 changed files with 40 additions and 352 deletions

32
app/react-components/pixi/extensions.js vendored Normal file
View File

@ -0,0 +1,32 @@
import {ExtensionType} from '@pixi/core';
import {Layer, Stage as LayerStage} from '@pixi/layers';
let AmbientLight, diffuseGroup, normalGroup, lightGroup;
if ('undefined' !== typeof window) {
({AmbientLight, diffuseGroup, normalGroup, lightGroup} = await import('@pixi/lights'));
}
export const ApplicationStageLayers = {
type: ExtensionType.Application,
priority: 100,
ref: {
destroy: function() {},
init: function() {
this.stage = new LayerStage();
},
},
};
export const ApplicationStageLights = {
type: ExtensionType.Application,
ref: {
destroy: function() {},
init: function() {
const {stage} = this;
stage.addChild(new Layer(diffuseGroup));
stage.addChild(new Layer(normalGroup));
stage.addChild(new Layer(lightGroup));
stage.addChild(new AmbientLight(0xffffff, 1));
},
},
};

View File

@ -1,5 +1,6 @@
import {SCALE_MODES} from '@pixi/constants';
import {BaseTexture} from '@pixi/core';
import {BaseTexture, extensions} from '@pixi/core';
import {Stage as PixiStage} from '@pixi/react';
import {createElement, useContext} from 'react';
import {RESOLUTION} from '@/constants.js';
@ -11,11 +12,16 @@ import MainEntityContext from '@/context/main-entity.js';
import RadiansContext from '@/context/radians.js';
import Ecs from './ecs.jsx';
import {ApplicationStageLayers, ApplicationStageLights} from './extensions.js';
import styles from './pixi.module.css';
import PixiStage from './stage.jsx';
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
extensions.add(
ApplicationStageLayers,
ApplicationStageLights,
);
const Contexts = [
AssetsContext,
ClientContext,

View File

@ -1,350 +0,0 @@
import { Application } from '@pixi/app';
import { Layer, Stage as LayerStage } from '@pixi/layers';
import { Ticker } from '@pixi/ticker';
import { AppProvider, PixiFiber } from '@pixi/react';
import PropTypes from 'prop-types';
import React from 'react';
const PROPS_DISPLAY_OBJECT = {
alpha: 1,
buttonMode: false,
cacheAsBitmap: false,
cursor: null,
filterArea: null,
filters: null,
hitArea: null,
interactive: false,
mask: null,
pivot: 0,
position: 0,
renderable: true,
rotation: 0,
scale: 1,
skew: 0,
transform: null,
visible: true,
x: 0,
y: 0,
};
let AmbientLight, diffuseGroup, normalGroup, lightGroup;
if ('undefined' !== typeof window) {
({AmbientLight, diffuseGroup, normalGroup, lightGroup} = await import('@pixi/lights'));
}
const noop = () => {};
/**
* -------------------------------------------
* Stage React Component (use this in react-dom)
*
* @usage
*
* const App = () => (
* <Stage
* width={500}
* height={500}
* options={ backgroundColor: 0xff0000 }
* onMount={( renderer, canvas ) => {
* console.log('PIXI renderer: ', renderer)
* console.log('Canvas element: ', canvas)
* }}>
* );
*
* -------------------------------------------
*/
const propTypes = {
// dimensions
width: PropTypes.number,
height: PropTypes.number,
// will return renderer
onMount: PropTypes.func,
onUnmount: PropTypes.func,
// run ticker at start?
raf: PropTypes.bool,
// render component on component lifecycle changes?
renderOnComponentChange: PropTypes.bool,
children: PropTypes.node,
// PIXI options, see https://pixijs.download/v7.x/docs/PIXI.Application.html
options: PropTypes.shape({
autoStart: PropTypes.bool,
width: PropTypes.number,
height: PropTypes.number,
useContextAlpha: PropTypes.bool,
backgroundAlpha: PropTypes.number,
autoDensity: PropTypes.bool,
antialias: PropTypes.bool,
preserveDrawingBuffer: PropTypes.bool,
resolution: PropTypes.number,
forceCanvas: PropTypes.bool,
backgroundColor: PropTypes.number,
clearBeforeRender: PropTypes.bool,
powerPreference: PropTypes.string,
sharedTicker: PropTypes.bool,
sharedLoader: PropTypes.bool,
}),
};
const defaultProps = {
width: 800,
height: 600,
onMount: noop,
onUnmount: noop,
raf: true,
renderOnComponentChange: true,
};
export function getCanvasProps(props)
{
const reserved = [
...Object.keys(propTypes),
...Object.keys(PROPS_DISPLAY_OBJECT),
];
return Object.keys(props)
.filter((p) => !reserved.includes(p))
.reduce((all, prop) => ({ ...all, [prop]: props[prop] }), {});
}
class Stage extends React.Component
{
_canvas = null;
_mediaQuery = null;
_ticker = null;
_needsUpdate = true;
app = null;
componentDidMount()
{
const {
onMount,
width,
height,
options,
raf,
renderOnComponentChange,
} = this.props;
this.app = new Application({
width,
height,
view: this._canvas,
...options,
autoDensity: options?.autoDensity !== false,
});
const stage = new LayerStage();
this.app.stage = stage;
stage.addChild(new Layer(diffuseGroup));
stage.addChild(new Layer(normalGroup));
stage.addChild(new Layer(lightGroup));
stage.addChild(new AmbientLight(0xffffff, 1));
if (process.env.NODE_ENV === 'development')
{
// workaround for React 18 Strict Mode unmount causing
// webgl canvas context to be lost
if (this.app.renderer.context?.extensions)
{
this.app.renderer.context.extensions.loseContext = null;
}
}
this.app.ticker.autoStart = false;
this.app.ticker[raf ? 'start' : 'stop']();
this.app.stage.__reactpixi = { root: this.app.stage };
this.mountNode = PixiFiber.createContainer(this.app.stage);
PixiFiber.updateContainer(this.getChildren(), this.mountNode, this);
onMount(this.app);
// update size on media query resolution change?
// only if autoDensity = true
if (
options?.autoDensity
&& window.matchMedia
&& options?.resolution === undefined
)
{
this._mediaQuery = window.matchMedia(
`(-webkit-min-device-pixel-ratio: 1.3), (min-resolution: 120dpi)`
);
this._mediaQuery.addListener(this.updateSize);
}
// listen for reconciler changes
if (renderOnComponentChange && !raf)
{
this._ticker = new Ticker();
this._ticker.autoStart = true;
this._ticker.add(this.renderStage);
this.app.stage.on(
'__REACT_PIXI_REQUEST_RENDER__',
this.needsRenderUpdate
);
}
this.updateSize();
this.renderStage();
}
componentDidUpdate(prevProps)
{
const { width, height, raf, renderOnComponentChange, options }
= this.props;
// update resolution
if (
options?.resolution !== undefined
&& prevProps?.options.resolution !== options?.resolution
)
{
this.app.renderer.resolution = options.resolution;
this.resetInteractionManager();
}
// update size
if (
prevProps.height !== height
|| prevProps.width !== width
|| prevProps.options?.resolution !== options?.resolution
)
{
this.updateSize();
}
// handle raf change
if (prevProps.raf !== raf)
{
this.app.ticker[raf ? 'start' : 'stop']();
}
// flush fiber
PixiFiber.updateContainer(this.getChildren(), this.mountNode, this);
if (
prevProps.width !== width
|| prevProps.height !== height
|| prevProps.raf !== raf
|| prevProps.renderOnComponentChange !== renderOnComponentChange
|| prevProps.options !== options
)
{
this._needsUpdate = true;
this.renderStage();
}
}
updateSize = () =>
{
const { width, height, options } = this.props;
if (!options?.resolution)
{
this.app.renderer.resolution = window.devicePixelRatio;
this.resetInteractionManager();
}
this.app.renderer.resize(width, height);
};
needsRenderUpdate = () =>
{
this._needsUpdate = true;
};
renderStage = () =>
{
const { renderOnComponentChange, raf } = this.props;
if (!raf && renderOnComponentChange && this._needsUpdate)
{
this._needsUpdate = false;
this.app.renderer.render(this.app.stage);
}
};
// provide support for Pixi v6 still
resetInteractionManager()
{
// `interaction` property is absent in Pixi v7 and in v6 if user has installed Federated Events API plugin.
// https://api.pixijs.io/@pixi/events.html
// in v7 however, there's a stub object which displays a deprecation warning, so also check the resolution property:
const { interaction: maybeInteraction } = this.app.renderer.plugins;
if (maybeInteraction?.resolution)
{
maybeInteraction.resolution = this.app.renderer.resolution;
}
}
getChildren()
{
const { children } = this.props;
return <AppProvider value={this.app}>{children}</AppProvider>;
}
componentDidCatch(error, errorInfo)
{
console.error(`Error occurred in \`Stage\`.`);
console.error(error);
console.error(errorInfo);
}
componentWillUnmount()
{
this.props.onUnmount(this.app);
if (this._ticker)
{
this._ticker.remove(this.renderStage);
this._ticker.destroy();
}
this.app.stage.off(
'__REACT_PIXI_REQUEST_RENDER__',
this.needsRenderUpdate
);
PixiFiber.updateContainer(null, this.mountNode, this);
if (this._mediaQuery)
{
this._mediaQuery.removeListener(this.updateSize);
this._mediaQuery = null;
}
this.app.destroy();
}
render()
{
const { options } = this.props;
if (options && options.view)
{
return null;
}
return (
<canvas
{...getCanvasProps(this.props)}
ref={(c) => (this._canvas = c)}
/>
);
}
}
Stage.propTypes = propTypes;
Stage.defaultProps = defaultProps;
export default Stage;