@playcanvas/react
Version:
A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.
372 lines • 16.4 kB
JavaScript
import entityConfig from './config/entity-config';
import * as componentConfig from './config/component-config';
import * as scriptConfig from './config/script-config';
const hosts = {
entity: entityConfig,
component: componentConfig,
// script: scriptConfig,
};
// const entityConfig = {
// createInstance: (_type: string, props: EntityProps, _app: pc.Application): EntityNode => {
// const entity = new pc.Entity(props.name);
// entityConfig.commitUpdate(entity, 'entity', {}, props);
// return { type: "entity", entity };
// },
// commitUpdate(instance: any, _type: string, oldProps: EntityProps, newProps: EntityProps) {
// console.log('commitUpdate', instance, _type, oldProps, newProps)
// },
// appendChild: (parent: EntityNode, child: EntityNode) => {
// if(parent.entity && child.entity) {
// parent.entity.addChild(child.entity);
// }
// },
// appendInitialChild: (parent: EntityNode, child: EntityNode) => {
// entityConfig.appendChild(parent, child);
// }
// }
// type ComponentProps = Record<string, unknown>;
// const componentConfig = {
// createInstance: (type: SystemKeys, props: ComponentProps, app: pc.Application): ComponentNode => {
// if (!app.systems[type as ComponentType]) {
// throw new Error(`Invalid component type: '${type}'`);
// }
// return {
// type: 'component',
// componentType: type,
// componentData: props,
// // componentProps: props,
// attachedTo: null,
// };
// },
// commitUpdate: (instance: ComponentNode, _type: string, _oldProps: ComponentProps, _newProps: ComponentProps) => {
// // console.log('commitUpdate', instance, _type, oldProps, newProps)
// const { attachedTo: entity, componentType, componentProps } = instance;
// if (!entity) {
// throw new Error('Component is not attached to an entity');
// }
// if (!entity[componentType as string]) {
// entity.addComponent(componentType, { ...componentProps });
// }
// // If component exists, update it
// const comp = entity[componentType as string];
// // If the component is a script, we need to create it
// if(componentType === 'script' && componentProps.script) {
// comp.create(componentProps.script, {
// properties: { ...componentProps },
// preloading: false,
// })
// }
// // Quick assign update
// Object.assign(comp, componentProps);
// },
// appendChild: (parent: EntityNode, child: ComponentNode) => {
// // const { type, componentType, componentData, componentProps } = child;
// // const { entity } = parent;
// if (parent.entity && child.type === 'component') {
// // componentConfig.commitUpdate(child, 'component', {}, child);
// // // If the component doesn’t exist yet, add it
// // if (entity[componentType as string] !== undefined) {
// // entity.addComponent(componentType, { ...componentData });
// // }
// // // @ts-ignore
// // const comp = entity[componentType];
// // // If the component is a script, we need to create it
// // if(componentType === 'script' && componentProps.script) {
// // comp.create(componentProps.script, {
// // properties: { ...componentProps },
// // preloading: false,
// // })
// // } else {
// // Object.assign(comp, componentProps);
// // }
// child.attachedTo = parent.entity;
// }
// },
// appendInitialChild: (parent: EntityNode, child: ComponentNode) => {
// componentConfig.appendChild(parent, child);
// }
// }
import {
// ContinuousEventPriority,
// DiscreteEventPriority,
DefaultEventPriority } from 'react-reconciler/constants';
/*********************************************************************
* The HostConfig for react-reconciler
*********************************************************************/
export const ReactPlayCanvasHostConfig = {
supportsMutation: true,
isPrimaryRenderer: false,
supportsPersistence: false,
supportsHydration: false,
warnsIfNotActing: false,
// now: () =>Date.now,
// @ts-ignore
resolveUpdatePriority: () => DefaultEventPriority,
getCurrentUpdatePriority: () => DefaultEventPriority,
setCurrentUpdatePriority: () => DefaultEventPriority,
maySuspendCommit: () => false,
// createContainerChildSet: () => { throw new Error('Persistence not supported') },
// appendChildToContainerChildSet: () => { throw new Error('Persistence not supported') },
// finalizeContainerChildren: () => { throw new Error('Persistence not supported') },
// replaceContainerChildren: () => { throw new Error('Persistence not supported') },
// cloneInstance: () => { throw new Error('Persistence not supported') },
// createTextInstance: () => { throw new Error('Text not supported') },
// preparePortalMount: () => { /* noop */ },
// scheduleTimeout: setTimeout,
// cancelTimeout: clearTimeout,
// getCurrentEventPriority: () => 99,
// getInstanceFromNode: () => null,
// beforeActiveInstanceBlur: () => { /* noop */ },
// afterActiveInstanceBlur: () => { /* noop */ },
prepareScopeUpdate: () => console.log('prepareScopeUpdate'),
// getInstanceFromScope: () => null,
prepareUpdate: () => console.log('prepareUpdate'),
getRootHostContext(rootContainerInstance) {
return { app: rootContainerInstance.app };
},
getChildHostContext(parentHostContext, _type, _rootContainerInstance) {
return parentHostContext;
},
getPublicInstance(instance) {
// The "public instance" is what a parent component would get if it held a ref
if (instance.type === 'entity') {
return instance.entity;
}
return instance;
},
prepareForCommit(_containerInfo) {
return null;
},
resetAfterCommit() { },
createInstance(type, props, { app }) {
// return hosts[type].createInstance(type, props, app);
// If the type is not found, use the component host
// let host;
switch (type) {
case 'entity':
return hosts['entity'].createInstance(type, props, app);
case 'pcscript':
return hosts['script'].createInstance(type, props, app);
default:
return hosts['component'].createInstance(type, props, app);
}
// const host = type === 'entity' ? hosts['entity'] : hosts['component'];
// return host.createInstance(type, props, app);
// switch(type) {
// case "entity":
// return entityConfig.createInstance(type, props, app);
// case "script":
// return scriptConfig.createInstance(type, props);
// default:
// return componentConfig.createInstance(type as componentConfig.SystemKeys, props, app);
// }
// if (type === "entity") {
// // @ts-ignore
// return entityConfig.createInstance(type, props, app);
// } else if (type === "script") {
// return scriptConfig.createInstance(type, props);
// } else {
// // @ts-ignore
// return componentConfig.createInstance(type, props, app);
// }
},
appendInitialChild(parent, child) {
console.log('appendInitialChild called with:', parent?.type, child?.type);
// const host = hosts[child.type];
// host.appendInitialChild(parent, child);
if (parent.type === 'entity' && child.type === 'entity') {
// Append child entity to parent entity
// console.log('appendInitialChild', parent, child)
entityConfig.appendInitialChild(parent, child);
}
else if (parent.type === 'entity' && child.type === 'component') {
// Attach component to the parent entity
// console.log('Append Component to Entity', parent, child)
componentConfig.appendInitialChild(parent, child);
// applyComponent(parent.entity, child.componentType, child.componentData);
// child.attachedTo = parent.entity;
}
else if (parent.type === 'component' && child.type === 'script') {
scriptConfig.appendInitialChild(parent, child);
}
else {
console.warn(`Cannot attach a ${child.type} directly to a ${parent.type}.`);
// e.g. attaching entity to a component makes no sense, or component->component
// In a more advanced scenario, you might handle special rules, but for now we ignore it.
}
},
finalizeInitialChildren() {
return false;
},
insertInContainerBefore(container, child, beforeChild) {
console.log('insertInContainerBefore', container, child, beforeChild);
// const scene = (container.getState().scene as unknown as Instance<THREE.Scene>['object']).__r3f
// if (!child || !beforeChild || !scene) return
// insertBefore(scene, child, beforeChild)
},
insertBefore() { },
commitUpdate(instance, type, oldProps, newProps) {
const host = hosts[instance.type];
console.log('commitUpdate');
host.commitUpdate(instance, type, oldProps, newProps);
},
shouldSetTextContent( /*type, props*/) {
// We never set text content on a 3D object
return false;
},
appendChild(parent, child) {
console.log('appendChild', parent, child);
// const host = hosts[child.type];
// host.appendChild(parent, child);
if (parent.type === 'entity' && child.type === 'entity') {
// Append child entity to parent entity
// console.log('appendChild', parent, child)
entityConfig.appendChild(parent, child);
}
else if (parent.type === 'entity' && child.type === 'component') {
// Attach component to the parent entity
// console.log('Append Component to Entity', parent, child)
componentConfig.appendChild(parent, child);
// applyComponent(parent.entity, child.componentType, child.componentData);
// child.attachedTo = parent.entity;
}
else if (parent.type === 'component' && child.type === 'script') {
scriptConfig.appendChild(parent, child);
}
else {
console.warn(`Cannot attach a ${child.type} directly to a ${parent.type}.`);
// e.g. attaching entity to a component makes no sense, or component->component
// In a more advanced scenario, you might handle special rules, but for now we ignore it.
}
// if (child.type === 'entity') {
// entityConfig.appendChild(parent, child);
// } else if (child.type === 'component') {
// componentConfig.appendChild(parent, child);
// }
// if (!parent || !child) {
// // console.warn('appendChild: parent or child is null');
// return;
// }
// if (parent.type === 'entity' && child.type === 'entity') {
// // console.log('Adding child entity to parent entity:');
// // parent.entity.addChild(child.entity);
// entityConfig.appendChild(parent, child);
// } else if (parent.type === 'entity' && child.type === 'component') {
// componentConfig.appendChild(parent, child);
// // applyComponent(parent.entity, child.componentType, child.componentData);
// // child.attachedTo = parent.entity;
// }
},
appendChildToContainer(container, child) {
// console.log('appendChildToContainer', container, child)
// const host = hosts[child.type];
// host.appendChildToContainer(container, child);
// if (child.type === 'entity') {
// // console.log('Adding child entity to parent entity:');
// // parent.entity.addChild(child.entity);
// entityConfig.appendChildToContainer(container, child);
// } else if (child.type === 'component') {
// componentConfig.appendChildToContainer(container, child);
// // applyComponent(parent.entity, child.componentType, child.componentData);
// // child.attachedTo = parent.entity;
// }
if (child.type === 'entity') {
// Append child entity to parent entity
console.log('appendInitialChild', child);
entityConfig.appendChildToContainer(container, child);
}
else {
console.warn(`Cannot attach a ${child.type} directly to the root.`);
// e.g. attaching entity to a component makes no sense, or component->component
// In a more advanced scenario, you might handle special rules, but for now we ignore it.
}
// The container is the "root container": we can assume it’s a { app: pc.Application } object
// For top-level Entities, you might automatically add them to app.root
// if (child.type === 'entity') {
// container.app.root.addChild(child.entity);
// } else if (child.type === 'component') {
// // If there's no existing entity, we can't attach the component
// // Typically you’d want a <Scene> or <Root> wrapper that’s an Entity
// console.warn('Cannot attach a component directly to the root. Wrap it in an <Entity>.');
// }
},
removeChild(parent, child) {
console.log('removeChild', parent, child);
const host = hosts[child.type];
host.removeChild(parent, child);
// if (parent.type === 'entity' && child.type === 'entity') {
// if (parent.entity.children.includes(child.entity)) {
// parent.entity.removeChild(child.entity);
// }
// } else if (parent.type === 'entity' && child.type === 'component' && child.attachedTo) {
// // removeComponent(parent.entity, child.componentType);
// // child.attachedTo = null;
// }
},
removeChildFromContainer(container, child) {
console.log('removeChildFromContainer', container, child);
const host = hosts[child.type];
host.removeChildFromContainer(container, child);
// if (child.type === 'entity') {
// // container.app.root.removeChild(child.entity);
// } else if (child.type === 'component' && child.attachedTo) {
// // removeComponent(child.attachedTo, child.componentType);
// // child.attachedTo = null;
// }
},
// Not used for this example
commitTextUpdate() {
// No text nodes
},
clearContainer() {
// Typically you don’t want to forcibly remove everything from the container
},
detachDeletedInstance: (instance) => {
console.log('detachDeletedInstance', instance);
},
// @ts-ignore
// setCurrentUpdatePriority(newPriority: EventPriority) {
// currentUpdatePriority = newPriority
// },
// // @ts-ignore
// getCurrentUpdatePriority() {
// return currentUpdatePriority
// },
// resolveUpdatePriority() {
// // if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority
// switch (typeof window !== 'undefined' && window.event?.type) {
// case 'click':
// case 'contextmenu':
// case 'dblclick':
// case 'pointercancel':
// case 'pointerdown':
// case 'pointerup':
// return DiscreteEventPriority
// case 'pointermove':
// case 'pointerout':
// case 'pointerover':
// case 'pointerenter':
// case 'pointerleave':
// case 'wheel':
// return ContinuousEventPriority
// default:
// return DefaultEventPriority
// }
// },
// @ts-ignore
// resolveUpdatePriority: (...args) => {
// console.log('resolveUpdatePriority', args)
// return DefaultEventPriority;
// },
// // @ts-ignore
// getCurrentUpdatePriority(...args) {
// console.log('getCurrentUpdatePriority', args)
// return DefaultEventPriority;
// },
// // @ts-ignore
// setCurrentUpdatePriority(...args) {
// console.log('setCurrentUpdatePriority', args)
// return DefaultEventPriority
// },
};
//# sourceMappingURL=hostConfig.js.map