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

WIP: Wasm Yoga port (2021) #67

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
18 changes: 9 additions & 9 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"index.js": {
"bundled": 15270,
"minified": 8092,
"gzipped": 2701,
"bundled": 9500,
"minified": 9415,
"gzipped": 3419,
"treeshaked": {
"rollup": {
"code": 127,
"import_statements": 127
"code": 8591,
"import_statements": 429
},
"webpack": {
"code": 1243
"code": 10302
}
}
},
Expand All @@ -19,8 +19,8 @@
"gzipped": 3407
},
"index.cjs": {
"bundled": 21936,
"minified": 10996,
"gzipped": 3444
"bundled": 12743,
"minified": 12730,
"gzipped": 4484
}
}
31 changes: 22 additions & 9 deletions examples/config-overrides.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
const { override, addWebpackAlias, addWebpackPlugin } = require('customize-cra')
const { override, addWebpackAlias, addWebpackPlugin, removeModuleScopePlugin, babelInclude } = require('customize-cra')
const { addReactRefresh } = require('customize-cra-react-refresh')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const path = require('path')

module.exports = override(
addReactRefresh(),
addWebpackAlias({
// three$: path.resolve('./src/utils/three.js'),
// '../../../build/three.module.js': path.resolve('./src/utils/three.js'),
}),
//addWebpackPlugin(new BundleAnalyzerPlugin())
)
module.exports = (config, env) => {
config.resolve.extensions = [...config.resolve.extensions, '.ts', '.tsx']
return override(
addReactRefresh(),
removeModuleScopePlugin(),
babelInclude([path.resolve('src'), path.resolve('../src')]),
addWebpackAlias({
'react-three-flex': path.resolve('../src/index'),
postprocessing: path.resolve('node_modules/postprocessing'),
react: path.resolve('node_modules/react'),
'react-dom': path.resolve('node_modules/react-dom'),
scheduler: path.resolve('node_modules/scheduler'),
'react-scheduler': path.resolve('node_modules/react-scheduler'),
'react-three-fiber': path.resolve('node_modules/react-three-fiber'),
drei: path.resolve('node_modules/drei'),
three$: path.resolve('./src/utils/three.js'),
'../../../build/three.module.js': path.resolve('./src/utils/three.js'),
}),
//addWebpackPlugin(new BundleAnalyzerPlugin())
)(config, env)
}
Binary file added examples/public/yoga.wasm
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"build-storybook": "build-storybook"
},
"dependencies": {
"yoga-layout-prebuilt": "^1.9.6"
"react-promise-suspense": "^0.3.3",
"yoga-wasm-slim": "^0.0.6"
},
"devDependencies": {
"@babel/core": "7.15.0",
Expand Down Expand Up @@ -105,6 +106,7 @@
"rimraf": "^3.0.2",
"rollup": "^2.26.10",
"rollup-plugin-filesize": "^9.1.1",
"rollup-plugin-size-snapshot": "^0.12.0",
"rollup-plugin-terser": "^7.0.2",
"three": "^0.131.3",
"ts-jest": "^27.0.4",
Expand Down
7 changes: 5 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import resolve from '@rollup/plugin-node-resolve'
import json from '@rollup/plugin-json'
import { terser } from 'rollup-plugin-terser'
import filesize from 'rollup-plugin-filesize'
import { sizeSnapshot } from 'rollup-plugin-size-snapshot'

const root = process.platform === 'win32' ? path.resolve('/') : '/'
const external = (id) => !id.startsWith('.') && !id.startsWith(root)
Expand Down Expand Up @@ -32,7 +33,8 @@ export default [
babel(getBabelOptions({ useESModules: true }, '>1%, not dead, not ie 11, not op_mini all')),
resolve({ extensions }),
terser(),
filesize()
filesize(),
sizeSnapshot(),
],
},
{
Expand All @@ -44,7 +46,8 @@ export default [
babel(getBabelOptions({ useESModules: false })),
resolve({ extensions }),
terser(),
filesize()
filesize(),
sizeSnapshot(),
],
},
]
17 changes: 9 additions & 8 deletions src/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useLayoutEffect, useRef, useMemo, useState } from 'react'
import * as THREE from 'three'
import Yoga from 'yoga-layout-prebuilt'
import { ReactThreeFiber, useFrame } from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'

import { setYogaProperties, rmUndefFromObj } from './util'
import { setYogaProperties, rmUndefFromObj, useYogaAsync } from './util'
import { boxContext, flexContext, SharedBoxContext } from './context'
import { R3FlexProps } from './props'
import { useReflow, useContext } from './hooks'
Expand Down Expand Up @@ -184,26 +183,28 @@ function BoxImpl(
wrap,
])

const yoga = useYogaAsync('./')

const { registerBox, unregisterBox, scaleFactor } = useContext(flexContext)
const { node: parent } = useContext(boxContext)
const group = useRef<THREE.Group>()
const node = useMemo(() => Yoga.Node.create(), [])
const node = useMemo(() => yoga._YGNodeNew(), [])
const reflow = useReflow()

useLayoutEffect(() => {
setYogaProperties(node, flexProps, scaleFactor)
setYogaProperties(yoga, node, flexProps, scaleFactor)
}, [flexProps, node, scaleFactor])

// Make child known to the parents yoga instance *before* it calculates layout
useLayoutEffect(() => {
if (!group.current || !parent) return

parent.insertChild(node, parent.getChildCount())
yoga._YGNodeInsertChild(parent, node, yoga._YGNodeGetChildCount(parent))
registerBox(node, group.current, flexProps, centerAnchor)

// Remove child on unmount
return () => {
parent.removeChild(node)
yoga._YGNodeRemoveChild(parent, node)
unregisterBox(node)
}
}, [node, parent, flexProps, centerAnchor, registerBox, unregisterBox])
Expand All @@ -217,10 +218,10 @@ function BoxImpl(
const epsilon = 1 / scaleFactor
useFrame(() => {
const width =
(typeof flexProps.width === 'number' ? flexProps.width : null) || node.getComputedWidth().valueOf() / scaleFactor
(typeof flexProps.width === 'number' ? flexProps.width : null) || yoga._YGNodeLayoutGetWidth(node) / scaleFactor
const height =
(typeof flexProps.height === 'number' ? flexProps.height : null) ||
node.getComputedHeight().valueOf() / scaleFactor
yoga._YGNodeLayoutGetHeight(node) / scaleFactor

if (Math.abs(width - size[0]) > epsilon || Math.abs(height - size[1]) > epsilon) {
setSize([width, height])
Expand Down
48 changes: 26 additions & 22 deletions src/Flex.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useLayoutEffect, useMemo, useCallback, PropsWithChildren, useRef } from 'react'
import Yoga, { YogaNode } from 'yoga-layout-prebuilt'
import * as THREE from 'three'
import { useFrame, useThree, ReactThreeFiber } from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'
import { YGNodeRef, CONSTANTS } from 'yoga-wasm-slim'

import {
setYogaProperties,
Expand All @@ -13,6 +13,8 @@ import {
getFlex2DSize,
getOBBSize,
getRootShift,
useYogaAsync,
setPropertyString,
} from './util'
import { boxContext, flexContext, SharedFlexContext, SharedBoxContext } from './context'
import type { R3FlexProps, FlexYogaDirection, FlexPlane } from './props'
Expand All @@ -37,7 +39,7 @@ export type FlexProps = PropsWithChildren<
Omit<ReactThreeFiber.Object3DNode<THREE.Group, typeof THREE.Group>, 'children'>
>
interface BoxesItem {
node: YogaNode
node: YGNodeRef
group: THREE.Group
flexProps: R3FlexProps
centerAnchor: boolean
Expand Down Expand Up @@ -223,7 +225,7 @@ function FlexImpl(
// Keeps track of the yoga nodes of the children and the related wrapper groups
const boxesRef = useRef<BoxesItem[]>([])
const registerBox = useCallback(
(node: YogaNode, group: THREE.Group, flexProps: R3FlexProps, centerAnchor: boolean = false) => {
(node: YGNodeRef, group: THREE.Group, flexProps: R3FlexProps, centerAnchor: boolean = false) => {
const i = boxesRef.current.findIndex((b) => b.node === node)
if (i !== -1) {
boxesRef.current.splice(i, 1)
Expand All @@ -232,17 +234,19 @@ function FlexImpl(
},
[]
)
const unregisterBox = useCallback((node: YogaNode) => {
const unregisterBox = useCallback((node: YGNodeRef) => {
const i = boxesRef.current.findIndex((b) => b.node === node)
if (i !== -1) {
boxesRef.current.splice(i, 1)
}
}, [])

const yoga = useYogaAsync('./')

// Reference to the yoga native node
const node = useMemo(() => Yoga.Node.create(), [])
const node = useMemo(() => yoga._YGNodeNew(), [])
useLayoutEffect(() => {
setYogaProperties(node, flexProps, scaleFactor)
setYogaProperties(yoga, node, flexProps, scaleFactor)
}, [node, flexProps, scaleFactor])

// Mechanism for invalidating and recalculating layout
Expand All @@ -266,7 +270,11 @@ function FlexImpl(
const depthAxis = getDepthAxis(plane)
const [flexWidth, flexHeight] = getFlex2DSize(size, plane)
const yogaDirection_ =
yogaDirection === 'ltr' ? Yoga.DIRECTION_LTR : yogaDirection === 'rtl' ? Yoga.DIRECTION_RTL : yogaDirection
yogaDirection === 'ltr'
? CONSTANTS.DIRECTION_LTR
: yogaDirection === 'rtl'
? CONSTANTS.DIRECTION_RTL
: yogaDirection

// Shared context for flex and box
const sharedFlexContext = useMemo<SharedFlexContext>(
Expand All @@ -288,14 +296,7 @@ function FlexImpl(
if (!disableSizeRecalc) {
// Recalc all the sizes
boxesRef.current.forEach(({ group, node, flexProps }) => {
const scaledWidth = typeof flexProps.width === 'number' ? flexProps.width * scaleFactor : flexProps.width
const scaledHeight = typeof flexProps.height === 'number' ? flexProps.height * scaleFactor : flexProps.height

if (scaledWidth !== undefined && scaledHeight !== undefined) {
// Forced size, no need to calculate bounding box
node.setWidth(scaledWidth)
node.setHeight(scaledHeight)
} else if (node.getChildCount() === 0) {
if (yoga._YGNodeGetChildCount(node) === 0 || flexProps.width === undefined || flexProps.height === undefined) {
// No size specified, calculate size
if (rootGroup.current) {
getOBBSize(group, rootGroup.current, boundingBox, vec)
Expand All @@ -304,17 +305,17 @@ function FlexImpl(
boundingBox.setFromObject(group).getSize(vec)
}

node.setWidth(scaledWidth || vec[mainAxis] * scaleFactor)
node.setHeight(scaledHeight || vec[crossAxis] * scaleFactor)
setPropertyString(yoga, node, 'Width', flexProps.width || vec[mainAxis], scaleFactor)
setPropertyString(yoga, node, 'Height', flexProps.height || vec[crossAxis], scaleFactor)
}
})
}

// Perform yoga layout calculation
node.calculateLayout(flexWidth * scaleFactor, flexHeight * scaleFactor, yogaDirection_)
yoga._YGNodeCalculateLayout(node, flexWidth * scaleFactor, flexHeight * scaleFactor, yogaDirection_)

const rootWidth = node.getComputedWidth()
const rootHeight = node.getComputedHeight()
const rootWidth = yoga._YGNodeLayoutGetWidth(node)
const rootHeight = yoga._YGNodeLayoutGetHeight(node)

let minX = 0
let maxX = 0
Expand All @@ -323,8 +324,11 @@ function FlexImpl(

// Reposition after recalculation
boxesRef.current.forEach(({ group, node, centerAnchor }) => {
const { left, top, width, height } = node.getComputedLayout()
const [mainAxisShift, crossAxisShift] = getRootShift(rootCenterAnchor, rootWidth, rootHeight, node)
const left = yoga._YGNodeLayoutGetLeft(node)
const top = yoga._YGNodeLayoutGetTop(node)
const width = yoga._YGNodeLayoutGetWidth(node)
const height = yoga._YGNodeLayoutGetHeight(node)
const [mainAxisShift, crossAxisShift] = getRootShift(rootCenterAnchor, rootWidth, rootHeight, yoga, node)

const position = vectorFromObject({
[mainAxis]: (mainAxisShift + left + (centerAnchor ? width / 2 : 0)) / scaleFactor,
Expand Down
8 changes: 4 additions & 4 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { createContext } from 'react'
import { YogaNode } from 'yoga-layout-prebuilt'
import { YGNodeRef } from 'yoga-wasm-slim'
import { Group } from 'three'
import { R3FlexProps } from './props'

export interface SharedFlexContext {
scaleFactor: number
requestReflow(): void
registerBox(node: YogaNode, group: Group, flexProps: R3FlexProps, centerAnchor?: boolean): void
unregisterBox(node: YogaNode): void
registerBox(node: YGNodeRef, group: Group, flexProps: R3FlexProps, centerAnchor?: boolean): void
unregisterBox(node: YGNodeRef): void
notInitialized?: boolean
}

Expand All @@ -28,7 +28,7 @@ const initialSharedFlexContext: SharedFlexContext = {
export const flexContext = createContext<SharedFlexContext>(initialSharedFlexContext)

export interface SharedBoxContext {
node: YogaNode | null
node: YGNodeRef | null
size: [number, number]
centerAnchor?: boolean
notInitialized?: boolean
Expand Down
6 changes: 4 additions & 2 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useContext as useContextImpl, useMemo } from 'react'
import { Mesh, Vector3 } from 'three'
import { flexContext, boxContext } from './context'
import { setPropertyString, useYogaAsync } from './util'

export function useContext<T extends { notInitialized?: boolean }>(context: React.Context<T>) {
let result = useContextImpl(context)
Expand Down Expand Up @@ -39,14 +40,15 @@ export function useFlexNode() {
export function useSetSize(): (width: number, height: number) => void {
const { requestReflow, scaleFactor } = useContext(flexContext)
const node = useFlexNode()
const yoga = useYogaAsync('./')

const sync = useCallback(
(width: number, height: number) => {
if (node == null) {
throw new Error('yoga node is null. sync size is impossible')
}
node.setWidth(width * scaleFactor)
node.setHeight(height * scaleFactor)
setPropertyString(yoga, node, 'Width', width, scaleFactor)
setPropertyString(yoga, node, 'Height', height, scaleFactor)
requestReflow()
},
[node, requestReflow]
Expand Down
18 changes: 4 additions & 14 deletions src/props.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { YogaFlexDirection, YogaAlign, YogaJustifyContent, YogaFlexWrap, YogaDirection } from 'yoga-layout-prebuilt'

export type FlexYogaDirection = YogaDirection | 'ltr' | 'rtl'
export type FlexYogaDirection = 'ltr' | 'rtl'
export type FlexPlane = 'xy' | 'yz' | 'xz'

export type Value = string | number

export type FlexDirection = YogaFlexDirection | 'row' | 'column' | 'row-reverse' | 'column-reverse'
export type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse'

export type JustifyContent =
| YogaJustifyContent
| 'center'
| 'flex-end'
| 'flex-start'
| 'space-between'
| 'space-evenly'
| 'space-around'
export type JustifyContent = 'center' | 'flex-end' | 'flex-start' | 'space-between' | 'space-evenly' | 'space-around'

export type Align =
| YogaAlign
| 'auto'
| 'baseline'
| 'center'
Expand All @@ -27,7 +17,7 @@ export type Align =
| 'space-between'
| 'stretch'

export type FlexWrap = YogaFlexWrap | 'no-wrap' | 'wrap' | 'wrap-reverse'
export type FlexWrap = 'no-wrap' | 'wrap' | 'wrap-reverse'

export type R3FlexProps = Partial<{
// Align
Expand Down
Loading