holodepth

Framework entegrasyonları · Vue + Three

Yükleme & onBeforeUnmount: Composable Kargo ve Dispose Borusu

Doku veya model ağdan gelirken bileşen ömrü biterse yarış ve sızıntı üretir. Bu sayfa Vue + Three'de composable asenkron yükleme ile onBeforeUnmount dispose hattını anlatır: Suspense merkezi değil · Three varlık ↔ bileşen ömrü.

Holodepth bu yaprağı Vue 101 veya R3F Suspense turunun kopyası olarak yazmaz. R3F asenkron köprü Yükleme & Suspense köprüsü yaprağındadır (useLoader kimliği, askı merkezi). Angular ömür pasaportu Yükleme & OnDestroy yaprağındadır (ngOnDestroy registry, RxJS iptal). Mount kökü Mount & kök sözleşmesi , kare metronom Kare döngüsü & watchEffect yaprağındadır. glTF şema format izleklerindedir; sıkıştırma Platform · Asset yaprağındadır.

Vue + Three yolculuğunun omurgası şu zincirde okunur: mount → harita → tik → kargo → bağ → kaçış. Bu sayfa kargo kapısıdır: asenkron varlık nerede yüklenir, unmount'ta GPU ne zaman salınır, KeepAlive neden «görünmez ama canlı» tuzağıdır? Önceki yaprak rAF stop siluetini mühürledi; burada geometry, material ve texture dispose derinliği yazılır.

Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Yük composable ömrüne bağlıdır. AbortController unmount'ta iptal eder. onBeforeUnmount dispose borusunu sırayla çalıştırır. KeepAlive pause eder ama GPU'yu öldürmez. Route ayrılışında yarış guard'ı zorunludur. URL allowlist dışına çıkılmaz. Vue Suspense isteğe bağlı köprüdür, merkez değildir. Sonraki kapı olay bağları ve picking'tir.»

Bu sayfanın sınırı · komşu konularla ayrım

Bu sayfa Vue + Three'de composable yükleme / onBeforeUnmount sahipliğini anlatır:

  • useAsyncAsset benzeri composable boru
  • AbortController · route leave yarışı
  • Dispose sırası: geometry → material → texture
  • KeepAlive önbellek tuzağı (pause ama GPU canlı)
  • URL allowlist · sınırsız kullanıcı fetch'i yok
  • Vue <Suspense> yalnızca isteğe bağlı köprü

Bilinçli olarak dışarıda bırakılan konular:

Kısa ayrım: Bu sayfa «composable kargo ömürle nasıl mühürlenir?» · R3F yükleme «Suspense askı kimliği» · Angular yükleme «DestroyRef iptal pasaportu» · Format «paket içinde ne var?».

Dispose sırası: geometry, material, texture

Three kaynakları Vue reaktivitesinin dışında GPU'da yaşar. Holodepth dispose borusunda sıra bilinçlidir: önce sahne grafından remove, ardından mesh'e özel geometry.dispose(), sonra materyal(ler) ve bağlı texture.dispose() çağrıları. Ters sıra veya atlama sessiz sızıntı üretir; DevTools bellek grafiği yavaş yükselir, sekme kapanınca düzelmez.

MeshStandardMaterial gibi çoklu doku taşıyan materyallerde map, normalMap, roughnessMap gibi alanlar ayrı ayrı kontrol edilir. Paylaşılan doku cache'den geliyorsa dispose etmezsiniz; yerel kopyaysa salarsınız. Holodepth kuralı: dispose kararı sahiplik modeline bağlıdır, «her unmount'ta her şeyi öldür» değildir.

Renderer ve WebGL context mount köküne aittir; parça bileşen renderer.dispose() çağırmaz. Child yalnızca kendi yüklediği geometri/materyal/dokuyu borudan geçirir. Bu ayrım Angular host registry ve R3F Fiber unmount semantiğiyle aynı diplomatiktir; Vue'da boru onBeforeUnmount içinde toplanır.

Yanlış dispose sırası, görünmez ama kalıcı GPU faturasıdır.

Composable asenkron yükleme

Holodepth Vue + Three varsayılanı: yükleme mantığını useAsyncAsset benzeri bir composable'da toplamak. Böylece fetch/loader başlangıcı, iptal jetonu, hata bayrağı ve dispose listesi tek yerde mühürlenir. Dağınık onMounted içi TextureLoader().load çağrıları ikinci boru riski taşır; composable «tek sahip» sözleşmesidir.

Composable dönüşü tipik olarak shallowRef ile işaretlenmiş Object3D veya Texture tutar; reaktif Proxy sahne grafına sızmaz (önceki harita yaprağı). Yükleme durumu için ayrı ref (idle / loading / ready / error) şablon fallback'ine köprü olur; bu, R3F Suspense merkezinin yerine geçen Vue diplomasisidir.

Imperatif Three'de Promise çözülünce scene.add edersiniz. Vue'da aynı iş composable içinde yapılır; şablon yalnızca «hazır mı?» sorusunu sorar. TresJS gibi kütüphaneler benzer yüzeyi deklaratif loader prop'larıyla sunabilir; Holodepth kütüphane katalogu açmaz, yalnızca «yükleme composable'da yaşar» ilkesini mühürler.

KeepAlive: pause ama GPU canlı

<KeepAlive> bileşeni DOM ağacında deaktive eder; onDeactivated çalışır, fakat onBeforeUnmount çalışmaz. Three geometri, materyal ve dokuları GPU'da yaşamaya devam eder. Holodepth: KeepAlive ile sarılmış 3D panel «görünmez ama faturalı» tuzağıdır. Sekme değişince Vue pause eder; bellek ve VRAM düşmez.

İki politika vardır. Önbellek bilinçli: deactivate'te yalnızca rAF durdurulur (önceki kare yaprağı), dispose yapılmaz; activate'te döngü devam eder. VRAM tasarrufu: deactivate'te ağır dokuları serbest bırakıp activate'te yeniden yükleme kabul edilir. Ürün cümlesi tek satır olmalı: KeepAlive cache mi, VRAM bütçesi mi?

onActivated / onDeactivated hook'ları dispose borusunun yerine geçmez; yalnızca pause/resume ve isteğe bağlı hafifletme için kullanılır. Unmount gerçekten geldiğinde onBeforeUnmount tam boruyu çalıştırmalıdır. Angular'da benzer «route reuse strategy» tuzağı vardır; Vue'da KeepAlive daha görünür bir yüzey sunar.

AbortController: iptal jetonu

Fetch tabanlı yüklemede AbortController standart iptal mekanizmasıdır. Composable her yük turunda yeni bir controller üretir; onBeforeUnmount veya prop değişiminde önceki controller abort() edilir. Çözüm geldiğinde sinyal düşmüşse sahneye dokunmayın; gelen buffer veya texture'ı hemen dispose edin veya hiç kabul etmeyin.

Three TextureLoader ve model loader'ları doğrudan AbortSignal almayabilir; Holodepth pratikte iki katman kullanır: fetch katmanında abort, loader katmanında «jeton bayrağı» guard. Guard false ise callback içindeki scene.add çalışmaz. Angular'daki takeUntilDestroyed ve R3F'in ağaç unmount semantiği aynı sorunu farklı API ile çözer; Vue'da AbortController + bayrak ikilisi yaygındır.

Aynı composable içinde üst üste iki yükleme başlatılırsa eski akış iptal edilir. Bu, Angular switchMap ruhunun Vue karşılığıdır. İptal edilmiş akışın hata callback'i sessizce yutulmamalı; en azından geliştirici logu veya metrik üretilmelidir.

Route ayrılışı: unmount yükü geçer

Vue Router ile hızlı gezinmede klasik hastalık: Promise çözülünce ölü bileşene parent.add(mesh) veya reaktif ref yazımı yapılır. Holodepth: route leave guard'ı veya composable içi «canlı mı?» bayrağı zorunludur. onBeforeRouteLeave yalnızca navigasyon onayı değildir; 3D panelde abort + dispose ön hazırlığıdır.

KeepAlive ile birleşince yarış daha sinsidir: route değişir, bileşen deactivate olur, yükleme arka planda biter ve activate olmayan önbelleğe yazar. Guard hem unmount hem deactivate yolunu kapsamalıdır. Nuxt onBeforeRouteLeave ve sayfa meta'sı aynı diplomatiktir; kurulum detayı framework rehberindedir, burada ömür kuralı yazılır.

Ölü rotanın canlı dokusu, sessiz sahne kirliliğidir.

onBeforeUnmount dispose borusu

Dağınık dispose() çağrıları unutulur. Holodepth pratikte bir kaynak listesi veya disposeSceneGraph(root) yardımcısı tutar. onBeforeUnmount sırası tipik olarak şöyledir: rAF stop (kare yaprağı), abort controller, sahne grafından remove, geometry dispose, material dispose, texture dispose, listener temizliği. Renderer kök mount'a aittir; child dokunmaz.

Üç sahiplik · karıştırmayın
Model Kim dispose eder? Ne zaman?
Bileşen yerel Yükleyen composable onBeforeUnmount
Pinia / servis cache Uygulama / bilinçli purge Ömür boyu veya manuel
KeepAlive önbellek Deactivate politikasına göre Activate veya gerçek unmount

Ortak materyali ilk çocuğun unmount'unda dispose etmek ikinci çocuğu öldürür. Cache modelinde dispose etmeyin; referans sayacı veya «misafir» sözleşmesi yazın. Vue provide ile paylaşılan loader cache'i mount yaprağındaki kök sözleşmeyle uyumlu olmalıdır.

URL allowlist: sınırsız fetch yok

Kullanıcı girdisini doğrudan loader URL'sine bağlamak SSRF ve bellek taşması riski taşır. Holodepth: panel asset'leri sabit allowlist veya sunucu tarafı çözümlenmiş kimlikle gelir. Dinamik seçimde önce anahtar doğrulanır, anahtar allowlist'ten gerçek path üretilir; ham string asla fetch'e gitmez.

Query ile cache busting bilinçli ürün kararıdır; rastgele query her mount'ta yeni indirme üretir. Versiyon hash'i build zamanında mühürlenir. Vue import.meta.url veya Vite asset import'u geliştirme kolaylığıdır; üretimde CDN path ve allowlist politikası ayrı yazılır.

Çoklu eşzamanlı büyük model indirmesi ağ ve decode bütçesini yer. Ürün tarafında eşzamanlılık tavanı ve boyut tavanı (platform asset yaprağı) composable ile birleştirilir; burada yalnızca «kullanıcı URL'si serbest değildir» kuralı mühürlenir.

Vue Suspense: isteğe bağlı köprü

Vue 3 <Suspense> asenkron setup ve async component için fallback sunar. Holodepth Vue + Three'de bunu merkez yapmaz: R3F track kimliği Suspense etrafındadır; Vue track'te composable boru ve durum ref'leri yeterlidir. Yine de ürün isterse async SFC'yi Suspense ile sarmak mümkündür; fallback iskelet veya düşük poli proxy mesh olmalıdır, boş siyah kutu değil.

Suspense sınırı tüm 3D paneli kapsarsa kaba ama basittir; parça parça sınır progressive reveal verir, layout zıplaması riski taşır. KeepAlive ile Suspense birlikte kullanıldığında deactivate yolunda fallback tekrar gösterilmez; önbellek politikası ayrı yazılır. Bu bölüm köprü notudur; derin Suspense müfredatı R3F yaprağına ve Vue resmi rehberine bırakılır.

Hata yüzeyi ve yarım yük

404, CORS, bozuk buffer veya abort sonrası sessiz siyah sahne üretebilir. Composable error ref'i şablona veya toast katmanına bağlanır; reject yutulmaz. Yarım yük (geometry geldi, texture gelmedi) durumunda kısmi kaynaklar borudan geri alınır; «yarım mesh» sahneye eklenmez.

Abort ile gerçek hata ayrımı önemlidir: abort kullanıcı/gezinme politikasıdır, hata değildir. UI'de abort için spinner sonsuza uzamaz; yeni rota veya yeni prop ile durum sıfırlanır. R3F Error Boundary ve Angular catchError kopyası değildir; Vue'da composable durum makinesi ürün standardıdır.

Kare yaprağı tiki ince tutmayı mühürledi. Fetch, parse, glTF decode ve texture upload'u her kareye yaymak jank üretir. Yük composable Promise yolunda bitsin; yerleşince demand veya sürekli loop tek kare boyasın. Ağır decode'u requestIdleCallback veya worker'a itmek ürün kararıdır; yükleme şeridi ile kare şeridi ayrı metronomdur.

Asenkron kargo tamamlanmadan rAF sahneye yazmamalıdır.

Güvenli ömür: simüle composable

Örnekler öğretim amaçlıdır; paket sürümünüzü doğrulayın. Sabit allowlist URL kullanın; kullanıcı string'i loader'a gitmez. Vue reaktivite temeli öğretilmez.

Simüle · useAsyncAsset: AbortController + guard + shallowRef. Mount'ta parent Object3D hazır kabul edilir.

import {
  onBeforeUnmount, shallowRef, ref, watch, type Ref, type ShallowRef,
} from 'vue'
import * as THREE from 'three'

const PANEL_TEXTURE = '/assets/demo/panel-albedo.jpg' // allowlist · kullanıcı string'i yok

type LoadStatus = 'idle' | 'loading' | 'ready' | 'error'

export function useAsyncTexture(
  parent: ShallowRef<THREE.Object3D | null>,
  url: string = PANEL_TEXTURE,
) {
  const status = ref<LoadStatus>('idle')
  const texture = shallowRef<THREE.Texture | null>(null)
  let controller: AbortController | null = null
  let alive = true

  const load = (nextUrl: string) => {
    controller?.abort()
    controller = new AbortController()
    const signal = controller.signal
    status.value = 'loading'

    fetch(nextUrl, { signal })
      .then((res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        return res.blob()
      })
      .then((blob) => {
        if (!alive || signal.aborted) return
        const objectUrl = URL.createObjectURL(blob)
        new THREE.TextureLoader().load(
          objectUrl,
          (tex) => {
            URL.revokeObjectURL(objectUrl)
            if (!alive || signal.aborted) {
              tex.dispose()
              return
            }
            tex.colorSpace = THREE.SRGBColorSpace
            texture.value = tex
            status.value = 'ready'
          },
          undefined,
          () => {
            URL.revokeObjectURL(objectUrl)
            if (alive && !signal.aborted) status.value = 'error'
          },
        )
      })
      .catch((err) => {
        if (err?.name === 'AbortError') return
        if (alive) status.value = 'error'
      })
  }

  watch(
    () => parent.value,
    (root) => {
      if (!root) return
      load(url)
    },
    { immediate: true },
  )

  onBeforeUnmount(() => {
    alive = false
    controller?.abort()
    texture.value?.dispose()
    texture.value = null
  })

  return { status, texture }
}

Simüle · dispose borusu: sıralı geometry / material / texture. Paylaşılan cache dokusuna dokunulmaz; yerel mesh tam salınır.

import { onBeforeUnmount, type ShallowRef } from 'vue'
import * as THREE from 'three'

const TEXTURE_KEYS = ['map', 'normalMap', 'roughnessMap', 'metalnessMap', 'aoMap'] as const

function disposeMaterial(material: THREE.Material, ownedTextures: Set<THREE.Texture>) {
  if ('dispose' in material && typeof material.dispose === 'function') {
    material.dispose()
  }
  if (material instanceof THREE.MeshStandardMaterial) {
    for (const key of TEXTURE_KEYS) {
      const tex = material[key]
      if (tex && ownedTextures.has(tex)) tex.dispose()
    }
  }
}

export function useMeshLifecycle(
  parent: ShallowRef<THREE.Object3D | null>,
  mesh: ShallowRef<THREE.Mesh | null>,
  ownedTextures: Set<THREE.Texture> = new Set(),
) {
  onBeforeUnmount(() => {
    const root = parent.value
    const m = mesh.value
    if (!m) return

    root?.remove(m)

    const geom = m.geometry
    if (geom) geom.dispose()

    const mat = m.material
    if (Array.isArray(mat)) {
      mat.forEach((item) => disposeMaterial(item, ownedTextures))
    } else if (mat) {
      disposeMaterial(mat, ownedTextures)
    }

    mesh.value = null
  })
}

/**
 * PanelPanel.vue içinde:
 *   const mesh = shallowRef<THREE.Mesh | null>(null)
 *   useMeshLifecycle(sceneRoot, mesh, ownedSet)
 * Renderer dispose mount kökünde · child çağırmaz.
 */

Güvenlik: kullanıcı girdisini URL olarak kullanmayın. Abort sonrası callback'te guard kontrolü yapın. KeepAlive deactivate'te dispose politikasını bilinçli seçin; varsayılan «hiç dispose etme» VRAM yer.

Yükleme / ömür tuzakları

  1. Unmount'sız yükleme: Promise ölü parent'a yazar.
  2. KeepAlive = dispose oldu sanmak: GPU canlı kalır.
  3. Paylaşılan dokuyu dispose: cache'i kırar.
  4. Kullanıcı URL'si: sınırsız fetch ve SSRF.
  5. Abort'sız fetch: route yarışı kazanır.
  6. Dispose sırası karışık: texture önce, geometry sonra.
  7. Renderer child'da dispose: tüm panel ölür.
  8. Suspense merkez sanmak: R3F kimliği Vue'ya taşınır.
  9. Decode her karede: jank ve frame drop.
  10. Proxy'li Texture ref: markRaw / shallowRef atlanmış.

Üç yükleme profili

  • 01 · Kargo

    Composable yerel

    Yükleme
    useAsyncAsset · allowlist
    Ömür
    onBeforeUnmount tam dispose
    Risk
    Abort guard unutulması
  • 02 · Kargo

    KeepAlive önbellek

    Deactivate
    rAF pause · dispose yok
    Unmount
    Tam boru çalışır
    Risk
    Görünmez VRAM
  • 03 · Kargo

    Servis cache + clone

    Okuma
    Paylaşılan GLTF kökü
    Yazma
    Scene.clone sahneye
    Risk
    Cache'i child dispose eder

Holodepth perspektifleri ve sonraki kapı

Composable boruyu tek sahip yapın

Yükleme, iptal ve dispose aynı composable'da mühürlenir; dağınık onMounted callback'leri ikinci boru üretir.

Abort her turda yenilenir

Prop veya route değişiminde eski fetch ölür; guard olmadan sahneye yazım yasaktır.

KeepAlive GPU'yu öldürmez

Deactivate pause politikasıdır; VRAM politikası ayrı yazılır. Gerçek unmount'ta tam boru çalışır.

Teşhis maddeleri

  1. Composable abort + alive guard var mı?
  2. onBeforeUnmount dispose sırası doğru mu?
  3. KeepAlive deactivate'te ne oluyor?
  4. URL allowlist dışına çıkılıyor mu?
  5. Paylaşılan cache yanlışlıkla dispose ediliyor mu?
  6. Renderer child'da dispose ediliyor mu?
  7. Decode rAF içine gömülü mü?
  8. Suspense merkez sanılıp composable atlanmış mı?

Holodepth içgörüsü

Vue + Three'de «bellek sızıntısı» çoğu zaman reaktif graf değil, ölü bileşene biten Promise ve KeepAlive'da unutulan GPU'dur. Önce composable boruyu okuyun, sonra şablon fallback'ini yazın.

Sonraki kapı: olay bağları & picking

Kargo sahneye yerleşince etkileşim başlar. Sonraki sayfa Olay bağları & picking pointer olayları, raycast ve reaktif seçim diplomasisini anlatır. Reaktif kaçış performans yaprağında derinleşir.