UNPKG

react-native-filament

Version:

A real-time physically based 3D rendering engine for React Native

236 lines (217 loc) 9.28 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import React from 'react'; import { FilamentProxy } from '../native/FilamentProxy'; import FilamentNativeView from '../native/specs/FilamentViewNativeComponent'; import { reportWorkletError } from '../ErrorUtils'; import { Context } from './Context'; import { findNodeHandle } from 'react-native'; /** * The component that wraps the native view. * @private */ export class FilamentView extends React.PureComponent { /** * Uses the context in class. * @note Not available in the constructor! */ static contextType = Context; // @ts-ignore constructor(props) { super(props); this.ref = /*#__PURE__*/React.createRef(); } get handle() { const nodeHandle = findNodeHandle(this.ref.current); if (nodeHandle == null || nodeHandle === -1) { throw new Error("Could not get the FilamentView's native view tag! Does the FilamentView exist in the native view-tree?"); } return nodeHandle; } updateTransparentRendering = enable => { const { renderer } = this.getContext(); renderer.setClearContent(enable); }; latestToken = 0; updateRenderCallback = async (callback, swapChain) => { var _this$renderCallbackL; const currentToken = ++this.latestToken; const { renderer, view, workletContext, _choreographer } = this.getContext(); // When requesting to update the render callback we have to assume that the previous one is not valid anymore // ie. its pointing to already released resources from useDisposableResource: (_this$renderCallbackL = this.renderCallbackListener) === null || _this$renderCallbackL === void 0 || _this$renderCallbackL.remove(); // Adding a new render callback listener is an async operation const listener = await workletContext.runAsync(() => { 'worklet'; // We need to create the function we pass to addFrameCallbackListener on the worklet thread, so that the // underlying JSI function is owned by that thread. Only then can we call it on the worklet thread when // the choreographer is calling its listeners. return _choreographer.addFrameCallbackListener(frameInfo => { 'worklet'; try { callback(frameInfo); // TODO: investigate the root cause of this. This should never happen, but we've seen sentry reports of it. if (!swapChain.isValid) { console.warn('[react-native-filament] SwapChain is invalid, cannot render frame.\nThis should never happen, please report an issue with reproduction steps.'); return; } if (renderer.beginFrame(swapChain, frameInfo.timestamp)) { renderer.render(view); renderer.endFrame(); } } catch (e) { reportWorkletError(e); } }); }); // As setting the listener is async, we have to check updateRenderCallback was called meanwhile. // In that case we have to assume that the listener we just set is not valid anymore: if (currentToken !== this.latestToken) { listener.remove(); return; } this.renderCallbackListener = listener; }; getContext = () => { if (this.context == null) { throw new Error('Filament component must be used within a FilamentProvider!'); } return this.context; }; componentDidMount() { // Setup transparency mode: if (!this.props.enableTransparentRendering) { this.updateTransparentRendering(false); } } componentDidUpdate(prevProps) { if (prevProps.enableTransparentRendering !== this.props.enableTransparentRendering) { this.updateTransparentRendering(this.props.enableTransparentRendering ?? true); } if (prevProps.renderCallback !== this.props.renderCallback && this.swapChain != null) { // Note: if swapChain was null, the renderCallback will be set/updated in onSurfaceCreated, which uses the latest renderCallback prop this.updateRenderCallback(this.props.renderCallback, this.swapChain); } } /** * Calling this signals that this FilamentView will be removed, and it should release all its resources and listeners. */ cleanupResources() { var _this$renderCallbackL2, _this$swapChain, _this$view; const { _choreographer } = this.getContext(); _choreographer.stop(); (_this$renderCallbackL2 = this.renderCallbackListener) === null || _this$renderCallbackL2 === void 0 || _this$renderCallbackL2.remove(); (_this$swapChain = this.swapChain) === null || _this$swapChain === void 0 || _this$swapChain.release(); this.swapChain = undefined; // Note: important to set it to undefined, as this might be called twice (onSurfaceDestroyed and componentWillUnmount), and we can only release once // Unlink the view from the choreographer. The native view might be destroyed later, after another FilamentView is created using the same choreographer (and then it would stop the rendering) (_this$view = this.view) === null || _this$view === void 0 || _this$view.setChoreographer(undefined); } componentWillUnmount() { var _this$surfaceCreatedL, _this$surfaceDestroye; (_this$surfaceCreatedL = this.surfaceCreatedListener) === null || _this$surfaceCreatedL === void 0 || _this$surfaceCreatedL.remove(); (_this$surfaceDestroye = this.surfaceDestroyedListener) === null || _this$surfaceDestroye === void 0 || _this$surfaceDestroye.remove(); this.cleanupResources(); } // This registers the surface provider, which will be notified when the surface is ready to draw on: onViewReady = async () => { const context = this.getContext(); const handle = this.handle; console.log('Finding FilamentView with handle', handle); this.view = await FilamentProxy.findFilamentView(handle); if (this.view == null) { throw new Error(`Failed to find FilamentView #${handle}!`); } console.log('Found FilamentView!'); // Link the view with the choreographer. // When the view gets destroyed, the choreographer will be stopped. this.view.setChoreographer(context._choreographer); if (this.ref.current == null) { throw new Error('Ref is not set!'); } const surfaceProvider = this.view.getSurfaceProvider(); this.surfaceCreatedListener = surfaceProvider.addOnSurfaceCreatedListener(() => { this.onSurfaceCreated(surfaceProvider); }, FilamentProxy.getCurrentDispatcher()); this.surfaceDestroyedListener = surfaceProvider.addOnSurfaceDestroyedListener(() => { this.onSurfaceDestroyed(); }, FilamentProxy.getCurrentDispatcher()); // Link the surface with the engine: console.log('Setting surface provider'); context.engine.setSurfaceProvider(surfaceProvider); // Its possible that the surface is already created, then our callback wouldn't be called // (we still keep the callback as on android a surface can be destroyed and recreated, while the view stays alive) if (surfaceProvider.getSurface() != null) { console.log('Surface already created!'); this.onSurfaceCreated(surfaceProvider); } }; // This will be called once the surface is created and ready to draw on: onSurfaceCreated = async surfaceProvider => { const { engine, workletContext, _choreographer } = this.getContext(); // Create a swap chain … const enableTransparentRendering = this.props.enableTransparentRendering ?? true; this.swapChain = await workletContext.runAsync(() => { 'worklet'; return engine.createSwapChainForSurface(surfaceProvider, enableTransparentRendering); }); // Apply the swapchain to the engine … engine.setSwapChain(this.swapChain); // Set the render callback in the choreographer: const { renderCallback } = this.props; await this.updateRenderCallback(renderCallback, this.swapChain); // Start the choreographer … _choreographer.start(); }; /** * On surface destroyed might be called multiple times for the same native view (FilamentView). * On android if a surface is destroyed, it can be recreated, while the view stays alive. */ onSurfaceDestroyed = () => { this.cleanupResources(); }; /** * Pauses the rendering of the Filament view. */ pause = () => { const { _choreographer } = this.getContext(); _choreographer.stop(); }; /** * Resumes the rendering of the Filament view. * It's a no-op if the rendering is already running. */ resume = () => { const { _choreographer } = this.getContext(); _choreographer.start(); }; /** @internal */ render() { return /*#__PURE__*/React.createElement(FilamentNativeView, _extends({ ref: this.ref, onViewReady: this.onViewReady }, this.props)); } } // @ts-expect-error Not in the types FilamentView.defaultProps = { enableTransparentRendering: true }; //# sourceMappingURL=FilamentView.js.map