Browse Source

Merge branch 'main' into bugfix/449-memory-management

alvarosabu 1 year ago
parent
commit
47ee274d91

+ 22 - 0
CHANGELOG.md

@@ -1,5 +1,27 @@
 
 
+## [3.6.0](https://github.com/Tresjs/tres/compare/3.5.2...3.6.0) (2023-12-12)
+
+
+### Features
+
+* enable devtools ([#465](https://github.com/Tresjs/tres/issues/465)) ([d61df05](https://github.com/Tresjs/tres/commit/d61df0597965e708581d1b32eafa2f14866bf9b4))
+
+## [3.6.0-next.0](https://github.com/Tresjs/tres/compare/3.5.1...3.6.0-next.0) (2023-12-06)
+
+
+### Features
+
+* devtools hook on window object ([df96c1f](https://github.com/Tresjs/tres/commit/df96c1f31aafe1b73cd344fde39420c1ae443d47))
+  
+
+## [3.5.2](https://github.com/Tresjs/tres/compare/3.5.1...3.5.2) (2023-12-12)
+
+
+### Bug Fixes
+
+* force memory free allocation when disposing materials and geometries ([#463](https://github.com/Tresjs/tres/issues/463)) ([1ef3533](https://github.com/Tresjs/tres/commit/1ef353363d02256275c483ec15ad424dc058991a))
+
 ## [3.5.1](https://github.com/Tresjs/tres/compare/3.5.0...3.5.1) (2023-11-28)
 
 ## [3.5.0](https://github.com/Tresjs/tres/compare/3.4.1...3.5.0) (2023-11-05)

+ 6 - 6
README.md

@@ -19,7 +19,7 @@ Tres (Spanish word for "three", pronounced `/tres/` ) is a way of creating Three
 
 It's build on-top of a [Vue Custom Renderer](https://vuejs.org/api/custom-renderer.html#createrenderer) and it's powered by Vite.
 
-The goal is to provide the Vue's community an easy way of building 3D scenes with Vue, always up to date with the latest ThreeJS features and with 0-to-none mantainance.
+The goal is to provide the Vue's community an easy way of building 3D scenes with Vue, always up to date with the latest ThreeJS features and with 0-to-none maintenance.
 
 ## Installation
 
@@ -39,11 +39,11 @@ Checkout the [docs](https://tresjs.org)
 
 | Package                     | Version                                                                                            |
 | --------------------------- | :------------------------------------------------------------------------------------------------- |
-| [Tres](packages/tres)       | ![tres version](https://img.shields.io/npm/v/@tresjs/core/latest.svg?label=%20&color=%2382DBCA)    |
-| [Cientos](packages/cientos) | ![cientos version](https://img.shields.io/npm/v/@tresjs/cientos/latest.svg?label=%20&color=%23f19b00) |
-| [Post-processing](packages/post-processing) | ![post-processing version](https://img.shields.io/npm/v/@tresjs/post-processing/latest.svg?label=%20&color=ff7bac) |
-| [Nuxt](packages/nuxt) | ![nuxt version](https://img.shields.io/npm/v/@tresjs/nuxt/latest.svg?label=%20&color=4f4f4f&logo=nuxt.js) |
-| [TresLeches 🍰](packages/leches) | ![tresleches version](https://img.shields.io/npm/v/@tresjs/leches/latest.svg?label=%20&color=ffffff) |
+| [Tres](https://github.com/TresJS/tres)       | ![tres version](https://img.shields.io/npm/v/@tresjs/core/latest.svg?label=%20&color=%2382DBCA)    |
+| [Cientos](https://github.com/TresJS/cientos) | ![cientos version](https://img.shields.io/npm/v/@tresjs/cientos/latest.svg?label=%20&color=%23f19b00) |
+| [Post-processing](https://github.com/TresJS/post-processing) | ![post-processing version](https://img.shields.io/npm/v/@tresjs/post-processing/latest.svg?label=%20&color=ff7bac) |
+| [Nuxt](https://github.com/TresJS/nuxt) | ![nuxt version](https://img.shields.io/npm/v/@tresjs/nuxt/latest.svg?label=%20&color=4f4f4f&logo=nuxt.js) |
+| [TresLeches 🍰](https://github.com/TresJS/leches) | ![tresleches version](https://img.shields.io/npm/v/@tresjs/leches/latest.svg?label=%20&color=ffffff) |
 
 ## Contribution
 

+ 3 - 3
docs/.vitepress/theme/components/LoveVueThreeJS.vue

@@ -13,7 +13,7 @@ const thirdRowRef = ref()
 const tl2r = gsap.timeline()
 const tl3r = gsap.timeline()
 
-const heightOfSignleSvg = 150
+const heightOfSingleSvg = 150
 
 async function restartAnimation() {
   gsap.to(secondRowRef.value.$el, {
@@ -36,13 +36,13 @@ onMounted(() => {
   tl2r.to(secondRowRef.value.$el, {
     delay: 1,
     duration: 2,
-    y: -(8 * heightOfSignleSvg),
+    y: -(8 * heightOfSingleSvg),
     ease: 'elastic.easeOut',
   })
   tl3r.to(thirdRowRef.value.$el, {
     delay: 1.25,
     duration: 2,
-    y: -(12 * heightOfSignleSvg),
+    y: -(12 * heightOfSingleSvg),
     ease: 'power1.out',
   })
 })

+ 1 - 1
docs/api/instances-arguments-and-props.md

@@ -83,7 +83,7 @@ You can also pass props to the component, for example, the `TresAmbientLight` ha
 
 ### Set
 
-All properties whose underlying object has a `.set()` method have a shortcut to recieve the value as an array. For example, the `TresPerspectiveCamera` has a `position` property, which is a `Vector3` object, so you can pass it to the component like this:
+All properties whose underlying object has a `.set()` method have a shortcut to receive the value as an array. For example, the `TresPerspectiveCamera` has a `position` property, which is a `Vector3` object, so you can pass it to the component like this:
 
 ```html
 <TresPerspectiveCamera :position="[1, 2, 3]" />

+ 1 - 1
docs/api/tres-canvas.md

@@ -77,7 +77,7 @@ renderer.shadowMap.type = PCFSoftShadowMap
 | **clearColor** | The color the renderer will use to clear the canvas. | `#000000` |
 | **context** | This can be used to attach the renderer to an existing [RenderingContext](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext) | |
 | **depth** | Whether the drawing buffer has a [depth buffer](https://en.wikipedia.org/wiki/Z-buffering) of at least 16 bits. | `true` |
-| **disableRender** | Disable render on requestAnimationFrame, usefull for PostProcessing | `false` |
+| **disableRender** | Disable render on requestAnimationFrame, useful for PostProcessing | `false` |
 | **failIfMajorPerformanceCaveat** | Whether the renderer creation will fail upon low performance is detected. See [WebGL spec](https://registry.khronos.org/webgl/specs/latest/1.0/#5.2) for details. | `false` |
 | **logarithmicDepthBuffer** | Whether to use a logarithmic depth buffer. It may be necessary to use this if dealing with huge differences in scale in a single scene. Note that this setting uses gl_FragDepth if available which disables the [Early Fragment Test](https://www.khronos.org/opengl/wiki/Early_Fragment_Test) optimization and can cause a decrease in performance. | `false` |
 | **outputColorSpace** | Defines the output encoding | `LinearEncoding` |

+ 2 - 2
docs/blog/tres-ecosystem-update-8-11-23.md

@@ -12,7 +12,7 @@ Lets jump right into it like it was a mountain of leaves. 🍂
 
 ## TresJS `v3.5.0`
 
-Core package has been updated to v3.5.0, this update includes a new feature that comes extrenemly handy when working with gltf models.
+Core package has been updated to v3.5.0, this update includes a new feature that comes extremely handy when working with gltf models.
 
 ### useSeek new methods
 
@@ -180,7 +180,7 @@ A highly requested bugfix that allows you to use CSS transitions on the `<Html /
 
 ## Time for dessert 🍰
 
-To finish this update, we have a new package in the ecosystem thats near to hit alpha soon. 🎉
+To finish this update, we have a new package in the ecosystem that's near to hit alpha soon. 🎉
 
 ![Tresleches package](/blog/tres-leches.png)
 

+ 1 - 1
docs/guide/troubleshooting.md

@@ -8,7 +8,7 @@ This guide is intended to help you solve the most common issues that you might e
 
 ## I can't see my 3D scene 😭!
 
-You followed the [Getting started guide](/guide/getting-started.md) but you still can see your scene renderered.
+You followed the [Getting started guide](/guide/getting-started.md) but you still can see your scene rendered.
 
 These are the most common reasons why you might not be able to see your scene:
 

+ 4 - 4
docs/package.json

@@ -8,11 +8,11 @@
     "build": "vitepress build",
     "preview": "vitepress preview"
   },
-  "dependencies": {
-    "@tresjs/core": "workspace:^3.1.1"
-  },
   "devDependencies": {
-    "unocss": "^0.57.7",
+    "unocss": "^0.58.0",
     "vite-svg-loader": "^5.1.0"
+  },
+  "dependencies": {
+    "@tresjs/core": "workspace:3.6.0-next.0"
   }
 }

+ 22 - 22
package.json

@@ -1,7 +1,7 @@
 {
   "name": "@tresjs/core",
   "type": "module",
-  "version": "3.5.1",
+  "version": "3.6.0",
   "packageManager": "pnpm@8.10.2",
   "description": "Declarative ThreeJS using Vue Components",
   "author": "Alvaro Saburido <hola@alvarosaburido.dev> (https://github.com/alvarosabu/)",
@@ -66,45 +66,45 @@
   },
   "dependencies": {
     "@alvarosabu/utils": "^3.1.1",
-    "@vueuse/core": "^10.5.0"
+    "@vueuse/core": "^10.7.0"
   },
   "devDependencies": {
     "@release-it/conventional-changelog": "^8.0.1",
     "@stackblitz/sdk": "^1.9.0",
     "@tresjs/cientos": "3.6.0",
     "@tresjs/eslint-config-vue": "^0.2.1",
-    "@types/three": "^0.158.3",
-    "@typescript-eslint/eslint-plugin": "^6.13.0",
-    "@typescript-eslint/parser": "^6.13.0",
-    "@vitejs/plugin-vue": "^4.5.0",
+    "@types/three": "^0.159.0",
+    "@typescript-eslint/eslint-plugin": "^6.14.0",
+    "@typescript-eslint/parser": "^6.14.0",
+    "@vitejs/plugin-vue": "^4.5.2",
     "@vitest/coverage-c8": "^0.33.0",
-    "@vitest/ui": "^0.34.6",
-    "@vue/test-utils": "^2.4.2",
-    "eslint": "^8.54.0",
-    "eslint-plugin-vue": "^9.18.1",
+    "@vitest/ui": "^1.0.4",
+    "@vue/test-utils": "^2.4.3",
+    "eslint": "^8.55.0",
+    "eslint-plugin-vue": "^9.19.2",
     "esno": "^4.0.0",
-    "gsap": "^3.12.2",
-    "jsdom": "^23.0.0",
+    "gsap": "^3.12.3",
+    "jsdom": "^23.0.1",
     "kolorist": "^1.8.0",
     "ohmyfetch": "^0.4.21",
     "pathe": "^1.1.1",
-    "release-it": "^17.0.0",
+    "release-it": "^17.0.1",
     "rollup-plugin-analyzer": "^4.0.0",
     "rollup-plugin-copy": "^3.5.0",
-    "rollup-plugin-visualizer": "^5.9.3",
-    "three": "^0.158.0",
-    "unocss": "^0.57.7",
+    "rollup-plugin-visualizer": "^5.11.0",
+    "three": "^0.159.0",
+    "unocss": "^0.58.0",
     "unplugin": "^1.5.1",
-    "unplugin-vue-components": "^0.25.2",
-    "vite": "^5.0.2",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.0.8",
     "vite-plugin-banner": "^0.7.1",
-    "vite-plugin-dts": "3.6.3",
-    "vite-plugin-inspect": "^0.8.0",
+    "vite-plugin-dts": "3.6.4",
+    "vite-plugin-inspect": "^0.8.1",
     "vite-plugin-require-transform": "^1.0.21",
     "vite-svg-loader": "^5.1.0",
     "vitepress": "1.0.0-rc.31",
-    "vitest": "^0.34.6",
-    "vue": "^3.3.9",
+    "vitest": "^1.0.4",
+    "vue": "^3.3.11",
     "vue-demi": "^0.14.6"
   }
 }

+ 5 - 4
playground/package.json

@@ -15,9 +15,10 @@
   "devDependencies": {
     "@tresjs/leches": "0.15.0-next.3",
     "@tweakpane/plugin-essentials": "^0.2.0",
-    "unplugin-auto-import": "^0.17.1",
-    "vite-plugin-glsl": "^1.2.0",
-    "vite-plugin-qrcode": "^0.2.2",
-    "vue-tsc": "^1.8.22"
+    "vite-plugin-vue-devtools": "1.0.0-rc.6",
+    "unplugin-auto-import": "^0.17.2",
+    "vite-plugin-glsl": "^1.2.1",
+    "vite-plugin-qrcode": "^0.2.3",
+    "vue-tsc": "^1.8.25"
   }
 }

+ 2 - 0
playground/vite.config.ts

@@ -7,11 +7,13 @@ import glsl from 'vite-plugin-glsl'
 import UnoCSS from 'unocss/vite'
 import { templateCompilerOptions } from '@tresjs/core'
 import { qrcode } from 'vite-plugin-qrcode'
+import VueDevTools from 'vite-plugin-vue-devtools'
 
 // https://vitejs.dev/config/
 export default defineConfig({
   plugins: [
     glsl(),
+    VueDevTools(),
     vue({
       script: {
         propsDestructure: true,

File diff suppressed because it is too large
+ 284 - 198
pnpm-lock.yaml


+ 1 - 1
src/composables/useCamera/index.ts

@@ -6,7 +6,7 @@ import type { TresContext } from '../useTresContextProvider'
 
 export const useCamera = ({ sizes, scene }: Pick<TresContext, 'sizes'> & { scene: TresScene }) => {
 
-  // the computed does not trigger, when for example the camera postion changes
+  // the computed does not trigger, when for example the camera position changes
   const cameras = ref<Camera[]>([])
   const camera = computed<Camera | undefined>(
     () => cameras.value[0],

+ 1 - 1
src/composables/useRaycaster/index.ts

@@ -22,7 +22,7 @@ export const useRaycaster = (
   objects: Ref<THREE.Object3D[]>,
   { renderer, camera, raycaster }: Pick<TresContext, 'renderer' | 'camera' | 'raycaster'>,
 ) => {
-  // having a seperate computed makes useElementBounding work
+  // having a separate computed makes useElementBounding work
   const canvas = computed(() => renderer.value.domElement as HTMLCanvasElement)
 
   const { x, y } = usePointer({ target: canvas })

+ 96 - 2
src/composables/useTresContextProvider/index.ts

@@ -1,8 +1,9 @@
-import { toValue, useElementSize, useWindowSize } from '@vueuse/core'
-import { inject, provide, readonly, shallowRef, computed, ref } from 'vue'
+import { toValue, useElementSize, useFps, useMemory, useRafFn, useWindowSize } from '@vueuse/core'
+import { inject, provide, readonly, shallowRef, computed, ref, onUnmounted } from 'vue'
 import type { Camera, EventDispatcher, Scene, WebGLRenderer } from 'three'
 import { Raycaster } from 'three'
 import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
+import { calculateMemoryUsage } from '../../utils/perf'
 import { useCamera } from '../useCamera'
 import type { UseRendererOptions } from '../useRenderer'
 import { useRenderer } from '../useRenderer'
@@ -17,6 +18,18 @@ export interface TresContext {
   controls: Ref<(EventDispatcher & { enabled: boolean }) | null>
   renderer: ShallowRef<WebGLRenderer>
   raycaster: ShallowRef<Raycaster>
+  perf: {
+    maxFrames: number
+    fps: {
+      value: number
+      accumulator: number[]
+    }
+    memory: {
+      currentMem: number
+      allocatedMem: number
+      accumulator: number[]
+    }
+  }
   registerCamera: (camera: Camera) => void
   setCameraActive: (cameraOrUuid: Camera | string) => void
   deregisterCamera: (camera: Camera) => void
@@ -78,6 +91,18 @@ export function useTresContextProvider({
     renderer,
     raycaster: shallowRef(new Raycaster()),
     controls: ref(null),
+    perf: {
+      maxFrames: 160,
+      fps: {
+        value: 0,
+        accumulator: [],
+      },
+      memory: {
+        currentMem: 0,
+        allocatedMem: 0,
+        accumulator: [],
+      },
+    },
     extend,
     registerCamera,
     setCameraActive,
@@ -85,6 +110,75 @@ export function useTresContextProvider({
   }
 
   provide('useTres', toProvide)
+
+  // Performance
+  const updateInterval = 100 // Update interval in milliseconds
+  const fps = useFps({ every: updateInterval }) 
+  const { isSupported, memory } = useMemory({ interval: updateInterval })
+  const maxFrames = 160
+  let lastUpdateTime = performance.now()
+
+  const updatePerformanceData = ({ timestamp }: { timestamp: number }) => {
+
+    // Update WebGL Memory Usage (Placeholder for actual logic)
+    // perf.memory.value = calculateMemoryUsage(gl)
+    if (toProvide.scene.value) {
+      toProvide.perf.memory.allocatedMem = calculateMemoryUsage(toProvide.scene.value as unknown as TresObject)
+    }
+    
+    // Update memory usage
+    if (timestamp - lastUpdateTime >= updateInterval) {
+      lastUpdateTime = timestamp
+
+      // Update FPS
+      toProvide.perf.fps.accumulator.push(fps.value as never)
+
+      if (toProvide.perf.fps.accumulator.length > maxFrames) {
+        toProvide.perf.fps.accumulator.shift()
+      }
+
+      toProvide.perf.fps.value = fps.value
+
+      // Update memory
+      if (isSupported.value && memory.value) {
+        toProvide.perf.memory.accumulator.push(memory.value.usedJSHeapSize / 1024 / 1024 as never)
+
+        if (toProvide.perf.memory.accumulator.length > maxFrames) {
+          toProvide.perf.memory.accumulator.shift()
+        }
+
+        toProvide.perf.memory.currentMem 
+        = toProvide.perf.memory.accumulator.reduce((a, b) => a + b, 0) / toProvide.perf.memory.accumulator.length
+        
+      }
+    }
+  }
+
+  // Devtools
+  let accumulatedTime = 0
+  const interval = 1 // Interval in milliseconds, e.g., 1000 ms = 1 second
+    
+  const { pause, resume } = useRafFn(({ delta }) => {
+    if (!window.__TRES__DEVTOOLS__) return
+
+    updatePerformanceData({ timestamp: performance.now() })
+    
+    // Accumulate the delta time
+    accumulatedTime += delta
+    
+    // Check if the accumulated time is greater than or equal to the interval
+    if (accumulatedTime >= interval) {
+      window.__TRES__DEVTOOLS__.cb(toProvide)
+    
+      // Reset the accumulated time
+      accumulatedTime = 0
+    }
+  }, { immediate: true }) 
+  
+  onUnmounted(() => {
+    pause()
+  })
+
   return toProvide
 }
 

+ 15 - 11
src/core/nodeOps.ts

@@ -16,6 +16,13 @@ let scene: TresScene | null = null
 
 const { logError } = useLogger()
 
+const supportedPointerEvents = [
+  'onClick',
+  'onPointerMove',
+  'onPointerEnter',
+  'onPointerLeave',
+]
+
 export const nodeOps: RendererOptions<TresObject, TresObject> = {
   createElement(tag, _isSVG, _anchor, props) {
     if (!props) props = {}
@@ -75,7 +82,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
   },
   insert(child, parent) {
     if (parent && parent.isScene) scene = parent as unknown as TresScene
-    
+
     const parentObject = parent || scene
 
     if (child?.isObject3D) {
@@ -87,10 +94,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       }
 
       if (
-        child?.onClick
-        || child?.onPointerMove
-        || child?.onPointerEnter
-        || child?.onPointerLeave
+        child && supportedPointerEvents.some(eventName => child[eventName])
       ) {
         if (!scene?.userData.tres__registerAtPointerEventHandler)
           throw 'could not find tres__registerAtPointerEventHandler on scene\'s userData'
@@ -145,10 +149,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
         throw 'could not find tres__deregisterAtPointerEventHandler on scene\'s userData'
 
       if (
-        object?.onClick
-          || object?.onPointerMove
-          || object?.onPointerEnter
-          || object?.onPointerLeave
+        object && supportedPointerEvents.some(eventName => object[eventName])
       )
         deregisterAtPointerEventHandler?.(object as Object3D)
     }
@@ -249,8 +250,11 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       if (value === '') value = true
       // Set prop, prefer atomic methods if applicable
       if (isFunction(target)) {
-        if (Array.isArray(value)) node[finalKey](...value)
-        else node[finalKey](value)
+        //don't call pointer event callback functions
+        if (!supportedPointerEvents.includes(prop)) {
+          if (Array.isArray(value)) node[finalKey](...value)
+          else node[finalKey](value)
+        }
         return
       }
       if (!target?.set && !isFunction(target)) root[finalKey] = value

+ 7 - 0
src/env.d.ts

@@ -7,5 +7,12 @@ declare module '*.vue' {
   export default component
 }
 
+interface Window {
+  __TRES__DEVTOOLS__?: {
+    cb: Function;
+    // You can add other properties of __TRES__DEVTOOLS__ here if needed
+  };
+}
+
 declare module '*.glsl' {}
 declare module '*.json' {}

+ 1 - 1
src/types/index.ts

@@ -126,7 +126,7 @@ export interface EventHandlers {
 }
 
 interface MathRepresentation {
-  set(...args: number[]): any
+  set(...args: number[] | [THREE.ColorRepresentation]): any
 }
 interface VectorRepresentation extends MathRepresentation {
   setScalar(s: number): any

+ 34 - 0
src/utils/perf.ts

@@ -0,0 +1,34 @@
+import type { Scene } from 'three'
+import type { TresObject } from './../types'
+
+export function calculateMemoryUsage(object: TresObject | Scene) {
+  let totalMemory = 0
+
+  object.traverse((node: TresObject) => {
+    if (node.isMesh && node.geometry) {
+      const geometry = node.geometry
+      const verticesMemory = geometry.attributes.position.count * 3 * Float32Array.BYTES_PER_ELEMENT
+      const facesMemory = geometry.index ? geometry.index.count * Uint32Array.BYTES_PER_ELEMENT : 0
+      const normalsMemory 
+        = geometry.attributes.normal ? geometry.attributes.normal.count * 3 * Float32Array.BYTES_PER_ELEMENT : 0
+      const uvsMemory = geometry.attributes.uv ? geometry.attributes.uv.count * 2 * Float32Array.BYTES_PER_ELEMENT : 0
+
+      const geometryMemory = verticesMemory + facesMemory + normalsMemory + uvsMemory
+      totalMemory += geometryMemory
+    }
+  })
+
+  return totalMemory
+}
+
+export function bytesToKB(bytes: number): string {
+  return (bytes / 1024).toFixed(2)
+}
+
+export function bytesToMB(bytes: number): string {
+  return (bytes / 1024 / 1024).toFixed(2)
+}
+
+export function bytesToGB(bytes: number): string {
+  return (bytes / 1024 / 1024 / 1024).toFixed(2)
+}

Some files were not shown because too many files changed in this diff