Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Examples Browser] Support device type switching #5147

Merged
merged 17 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
14 changes: 7 additions & 7 deletions examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@monaco-editor/react": "^4.4.5",
"@playcanvas/eslint-config": "^1.1.1",
"@playcanvas/observer": "1.3.6",
"@playcanvas/pcui": "^4.0.0",
"@playcanvas/pcui": "^4.0.3",
"@rollup/plugin-alias": "^4.0.2",
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-node-resolve": "^13.3.0",
Expand Down
1 change: 1 addition & 0 deletions examples/scripts/iframe/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function buildExample(category, filename) {
fs.writeFileSync(`${MAIN_DIR}/dist/iframe/${category}/${filename.replace(".tsx", "")}.html`, loadHtmlTemplate({
exampleClass: exampleClass,
enginePath: process.env.ENGINE_PATH || enginePath,
webgpuEnabled: formatters.getWebgpuEnabledFromClass(exampleClass),
miniStats: !formatters.classIncludesMiniStats(exampleClass)
}));
}
Expand Down
10 changes: 9 additions & 1 deletion examples/scripts/iframe/index.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
presets: ["react", "typescript", "env"]
}).code;
}
window.exampleFunction = new Function('canvas', 'data', exampleFunction);
window.exampleFunction = new Function('canvas', 'deviceType', 'data', exampleFunction);
}
window.loadFunction = example.load;
window.files = window.top.editedFiles || example.constructor.FILES;
Expand Down Expand Up @@ -181,6 +181,14 @@
return data;
} else if (arg === 'pcx') {
return pcx;
} else if (arg === 'deviceType') {
if ({{{webgpuEnabled}}}) {
return window.top.preferredGraphicsDevice || 'webgpu';
} else if (['webgl1', 'webgl2'].includes(window.top.preferredGraphicsDevice)) {
return window.top.preferredGraphicsDevice;
} else {
return 'webgl2';
}
}
});
window.exampleFunction.apply(this, args);
Expand Down
3 changes: 2 additions & 1 deletion examples/src/app/code-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ const CodeEditor = (props: CodeEditorProps) => {
options={{
scrollbar: {
horizontal: 'visible'
}
},
readOnly: false
}}
/>
</Panel>;
Expand Down
14 changes: 7 additions & 7 deletions examples/src/app/control-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import MonacoEditor from "@monaco-editor/react";
import { Button, Container, Panel } from '@playcanvas/pcui/react';
import { Button, Container } from '@playcanvas/pcui/react';

const ControlPanel = (props: any) => {
const [state, setState] = useState({
Expand All @@ -17,8 +17,8 @@ const ControlPanel = (props: any) => {
showCode: false,
collapsed: false
});
document.getElementById('paramButton').classList.toggle('selected');
document.getElementById('codeButton').classList.toggle('selected');
document.getElementById('paramButton').classList.add('selected');
document.getElementById('codeButton').classList.remove('selected');
const controls = document.getElementById('controlPanel-controls');
controls.classList.remove('pcui-hidden');
};
Expand All @@ -31,8 +31,8 @@ const ControlPanel = (props: any) => {
showCode: true,
collapsed: false
});
document.getElementById('paramButton').classList.toggle('selected');
document.getElementById('codeButton').classList.toggle('selected');
document.getElementById('paramButton').classList.remove('selected');
document.getElementById('codeButton').classList.add('selected');
const controls = document.getElementById('controlPanel-controls');
controls.classList.add('pcui-hidden');
};
Expand All @@ -48,7 +48,7 @@ const ControlPanel = (props: any) => {
}
});

return <Panel id='controlPanel' class={[window.top.innerWidth > 600 && !props.controls ? 'empty' : 'null', window.top.innerWidth < 601 ? 'mobile' : null]} resizable='top' headerText={window.top.innerWidth < 601 ? (props.controls ? 'CODE & CONTROLS' : 'CODE') : 'CONTROLS'} collapsible={true} collapsed={state.collapsed}>
return <Container id='controls-wrapper' class={props.controls ? 'has-controls' : null}>
{ window.top.innerWidth < 601 && props.controls && <Container id= 'controlPanel-tabs' class='tabs-container'>
<Button text='CODE' id='codeButton' class={state.showCode ? 'selected' : null} onClick={onClickCodeTab}/>
<Button text='PARAMETERS' class={state.showParameters ? 'selected' : null} id='paramButton' onClick={onClickParametersTab} />
Expand All @@ -65,7 +65,7 @@ const ControlPanel = (props: any) => {
value={props.files ? props.files[0].text : ''}
/>
}
</Panel>;
</Container>;
};

export default ControlPanel;
129 changes: 117 additions & 12 deletions examples/src/app/example.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import React, { Component } from 'react';
import { Container, Spinner } from '@playcanvas/pcui/react';
import { Container, Spinner, SelectInput, Panel } from '@playcanvas/pcui/react';
import { SelectInput as SelectInputClass } from '@playcanvas/pcui';
import { File } from './helpers/types';
import examples from './helpers/example-data.mjs';
// @ts-ignore: library file import
import { withRouter } from 'react-router-dom';
import ControlPanel from './control-panel';
import { Observer } from '@playcanvas/observer';

const DEVICETYPE = {
WEBGL1: 'webgl1',
WEBGL2: 'webgl2',
WEBGPU: 'webgpu'
};

const deviceTypeNames = {
[DEVICETYPE.WEBGL1]: 'WebGL 1',
[DEVICETYPE.WEBGL2]: 'WebGL 2',
[DEVICETYPE.WEBGPU]: 'WebGPU'
};

const controlsObserver = new Observer();

const Controls = (props: any) => {
const controlsFunction = (examples as any).paths[props.path].example.prototype.controls;
const controls = controlsFunction ? (examples as any).paths[props.path].example.prototype.controls((window as any).observerData).props.children : null;
if (!controls) return null;
// on desktop dont show the control panel when no controls are present
if (!controls && window.top.innerWidth > 600) return null;
return <ControlPanel controls={controls} files={props.files} />;
};
interface ControlLoaderProps {
Expand Down Expand Up @@ -38,6 +55,17 @@ class ControlLoader extends Component <ControlLoaderProps, ControlLoaderState> {
this.setState({
exampleLoaded: true
});


const pollHandler = setInterval(appCreationPoll, 50);
function appCreationPoll() {
if ((window as any).pc.app) {
clearInterval(pollHandler);
const app: { graphicsDevice: { activeDeviceType: string } } = (window as any).pc.app;
const activeDevice = app.graphicsDevice.activeDeviceType;
controlsObserver.emit('updateActiveDevice', activeDevice);
}
}
});
}

Expand Down Expand Up @@ -71,14 +99,85 @@ interface ExampleState {

class Example extends Component <ExampleProps, ExampleState> {
editorValue: string;
deviceTypeSelectInputRef;

constructor(props: any) {
super(props);
this.deviceTypeSelectInputRef = React.createRef();

controlsObserver.on('updateActiveDevice', this.onSetActiveGraphicsDevice);
}

get defaultFiles() {
return (examples as any).paths[this.path].files;
}

get path() {
return `/${this.props.match.params.category}/${this.props.match.params.example}`;
}

get deviceTypeSelectInput() {
return (this.deviceTypeSelectInputRef.current as { element: SelectInputClass }).element;
}

set preferredGraphicsDevice(value: string) {
(window as any).preferredGraphicsDevice = value;
}

get preferredGraphicsDevice() {
return (window as any).preferredGraphicsDevice;
}

setDisabledOptions = (preferredDevice = 'webgpu', activeDevice: string) => {
const selectInput = this.deviceTypeSelectInput;
if ((preferredDevice === DEVICETYPE.WEBGL2 || preferredDevice === DEVICETYPE.WEBGPU) && activeDevice === DEVICETYPE.WEBGL1) {
selectInput.fallbackOrder = [DEVICETYPE.WEBGPU, DEVICETYPE.WEBGL2, DEVICETYPE.WEBGL1];
selectInput.disabledOptions = {
[DEVICETYPE.WEBGPU]: 'WebGPU (not supported)',
[DEVICETYPE.WEBGL2]: 'WebGL 2 (not supported)'
};
} else if (preferredDevice === DEVICETYPE.WEBGL1 && activeDevice === DEVICETYPE.WEBGL2) {
selectInput.fallbackOrder = [DEVICETYPE.WEBGL1, DEVICETYPE.WEBGL2, DEVICETYPE.WEBGPU];
selectInput.disabledOptions = {
[DEVICETYPE.WEBGL1]: 'WebGL 1 (not supported)'
};
} else if (preferredDevice === DEVICETYPE.WEBGPU && activeDevice !== DEVICETYPE.WEBGPU) {
selectInput.fallbackOrder = [DEVICETYPE.WEBGPU, DEVICETYPE.WEBGL2, DEVICETYPE.WEBGL1];
selectInput.disabledOptions = {
[DEVICETYPE.WEBGPU]: 'WebGPU (not supported)'
};
} else {
selectInput.disabledOptions = null;
}
};

onSetActiveGraphicsDevice = (value: string) => {
if (!this.preferredGraphicsDevice) {
this.preferredGraphicsDevice = value;
this.deviceTypeSelectInput.value = value;
}
this.setDisabledOptions(this.preferredGraphicsDevice, value);
};

onSetPreferredGraphicsDevice = (value: string) => {
this.deviceTypeSelectInput.disabledOptions = null;
this.deviceTypeSelectInput.value = value;
this.preferredGraphicsDevice = value;
// reload the iframe after updating the device
const exampleIframe: HTMLIFrameElement = document.getElementById('exampleIframe') as HTMLIFrameElement;
exampleIframe.contentWindow.location.reload();
};

componentDidMount() {
window.localStorage.removeItem(this.path);
this.props.setFiles(this.defaultFiles);
}

shouldComponentUpdate(nextProps: Readonly<ExampleProps>): boolean {
return this.props.match.params.category !== nextProps.match.params.category || this.props.match.params.example !== nextProps.match.params.example;
const updateMobileOnFileChange = () => {
return window.top.innerWidth < 601 && this.props.files !== nextProps.files;
ellthompson marked this conversation as resolved.
Show resolved Hide resolved
};
return this.props.match.params.category !== nextProps.match.params.category || this.props.match.params.example !== nextProps.match.params.example || updateMobileOnFileChange();
}

componentDidUpdate() {
Expand All @@ -87,20 +186,26 @@ class Example extends Component <ExampleProps, ExampleState> {
this.props.setFiles(this.defaultFiles);
}

get defaultFiles() {
return (examples as any).paths[this.path].files;
}

get path() {
return `/${this.props.match.params.category}/${this.props.match.params.example}`;
}

render() {
const iframePath = `/iframe${this.path}`;
return <Container id="canvas-container">
<Spinner size={50}/>
<iframe id="exampleIframe" key={iframePath} src={iframePath}></iframe>
<ControlLoader path={this.path} files={this.props.files} />
<Panel id='controlPanel' class={[window.top.innerWidth < 601 ? 'mobile' : null]} resizable='top' headerText={window.top.innerWidth < 601 ? 'CODE & CONTROLS' : 'CONTROLS'} collapsible={true} collapsed={window.top.innerWidth < 601}>
<SelectInput
id='deviceTypeSelectInput'
options={[
{ t: deviceTypeNames[DEVICETYPE.WEBGL1], v: DEVICETYPE.WEBGL1 },
{ t: deviceTypeNames[DEVICETYPE.WEBGL2], v: DEVICETYPE.WEBGL2 },
{ t: deviceTypeNames[DEVICETYPE.WEBGPU], v: DEVICETYPE.WEBGPU }
]}
onSelect={this.onSetPreferredGraphicsDevice}
prefix='Active Device: '
// @ts-ignore this is setting a legacy ref
ref={this.deviceTypeSelectInputRef}
/>
<ControlLoader path={this.path} files={this.props.files} />
</Panel>
</Container>;
}
}
Expand Down
10 changes: 9 additions & 1 deletion examples/src/app/helpers/formatters.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const findClosingBracketMatchIndex = (str, pos) => {

const getTypeScriptFunctionFromText = (text) => {
const transformedCode = text;
const functionSignatureStartString = 'example(canvas: HTMLCanvasElement';
const functionSignatureStartString = 'example(canvas: HTMLCanvasElement, deviceType: pc.DEVICETYPE';
const indexOfFunctionSignatureStart = transformedCode.indexOf(functionSignatureStartString);
const functionSignatureEndString = '): void ';
const indexOfFunctionSignatureEnd = indexOfFunctionSignatureStart + transformedCode.substring(indexOfFunctionSignatureStart).indexOf(functionSignatureEndString) + functionSignatureEndString.length;
Expand Down Expand Up @@ -72,6 +72,13 @@ const getEngineTypeFromClass = (text) => {
return null;
};

const getWebgpuEnabledFromClass = (text) => {
if (text.indexOf(`_defineProperty(Example, "WEBGPU_ENABLED", true);`) !== -1) {
return true;
}
return false;
};

const classIncludesMiniStats = (text) => {
return text.includes('_defineProperty(Example, "MINISTATS", true);');
};
Expand All @@ -90,6 +97,7 @@ export default {
getInnerFunctionText: getInnerFunctionText,
getExampleClassFromTextFile: getExampleClassFromTextFile,
getEngineTypeFromClass: getEngineTypeFromClass,
getWebgpuEnabledFromClass: getWebgpuEnabledFromClass,
retrieveStaticObject: retrieveStaticObject,
classIncludesMiniStats: classIncludesMiniStats
};
10 changes: 8 additions & 2 deletions examples/src/examples/animation/blend-trees-1d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class BlendTrees1DExample {
</>;
}

example(canvas: HTMLCanvasElement, data: any): void {
example(canvas: HTMLCanvasElement, deviceType: pc.DEVICETYPE, data: any): void {

const assets = {
'model': new pc.Asset('model', 'container', { url: '/static/assets/models/bitmoji.glb' }),
Expand All @@ -26,7 +26,13 @@ class BlendTrees1DExample {
'bloom': new pc.Asset('bloom', 'script', { url: '/static/scripts/posteffects/posteffect-bloom.js' })
};

pc.createGraphicsDevice(canvas).then((device: pc.GraphicsDevice) => {
const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: '/static/lib/glslang/glslang.js',
twgslUrl: '/static/lib/twgsl/twgsl.js'
};

pc.createGraphicsDevice(canvas, gfxOptions).then((device: pc.GraphicsDevice) => {

const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class BlendTrees2DCartesianExample {
</>;
}

example(canvas: HTMLCanvasElement): void {
example(canvas: HTMLCanvasElement, deviceType: pc.DEVICETYPE): void {

const app = new pc.Application(canvas, {
mouse: new pc.Mouse(document.body),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class BlendTrees2DDirectionalExample {
</>;
}

example(canvas: HTMLCanvasElement): void {
example(canvas: HTMLCanvasElement, deviceType: pc.DEVICETYPE): void {

const app = new pc.Application(canvas, {
mouse: new pc.Mouse(document.body),
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/animation/component-properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ComponentPropertiesExample {
}


example(canvas: HTMLCanvasElement, data: any): void {
example(canvas: HTMLCanvasElement, deviceType: pc.DEVICETYPE, data: any): void {

const app = new pc.Application(canvas, {
mouse: new pc.Mouse(document.body),
Expand Down
Loading