@react-three/fiber 10.0.0-canary.0f7fc9b → 10.0.0-canary.0fc81c2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -915,7 +915,7 @@ const createStore = (invalidate, advance) => {
915
915
  size: newSize,
916
916
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
917
917
  }));
918
- getScheduler().invalidate();
918
+ get().invalidate();
919
919
  }
920
920
  }
921
921
  return;
@@ -930,15 +930,16 @@ const createStore = (invalidate, advance) => {
930
930
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
931
931
  _sizeImperative: true
932
932
  }));
933
- getScheduler().invalidate();
933
+ get().invalidate();
934
934
  },
935
935
  setDpr: (dpr) => set((state2) => {
936
936
  const resolved = calculateDpr(dpr);
937
937
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
938
938
  }),
939
939
  setFrameloop: (frameloop = "always") => {
940
+ const rootId = get().internal.rootId;
941
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
940
942
  set(() => ({ frameloop }));
941
- getScheduler().frameloop = frameloop;
942
943
  },
943
944
  setError: (error) => set(() => ({ error })),
944
945
  error: null,
@@ -1057,7 +1058,8 @@ const createStore = (invalidate, advance) => {
1057
1058
  const resizeTarget = canvasTarget ?? actualRenderer;
1058
1059
  if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1059
1060
  resizeTarget.setSize(size.width, size.height, false);
1060
- internal.canvasTargetSizeDirty = true;
1061
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1062
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1061
1063
  }
1062
1064
  if (camera !== oldCamera) {
1063
1065
  oldCamera = camera;
@@ -1243,6 +1245,13 @@ function useFrame$1(callback, priorityOrOptions) {
1243
1245
  * Use for controlling the entire frame loop, adding phases, etc.
1244
1246
  */
1245
1247
  scheduler: scheduler2,
1248
+ /**
1249
+ * The root that currently owns this job. Resolve on access because ambient
1250
+ * jobs can be adopted by a Canvas after the controls object is created.
1251
+ */
1252
+ get rootId() {
1253
+ return getScheduler().getJobRootId(id);
1254
+ },
1246
1255
  /**
1247
1256
  * Manually step this job only.
1248
1257
  * Bypasses FPS limiting - always runs.
@@ -1259,6 +1268,14 @@ function useFrame$1(callback, priorityOrOptions) {
1259
1268
  stepAll: (timestamp) => {
1260
1269
  getScheduler().step(timestamp);
1261
1270
  },
1271
+ /**
1272
+ * Request frames for this job's owning root without waking sibling roots.
1273
+ */
1274
+ invalidate: (frames, stackFrames) => {
1275
+ const scheduler3 = getScheduler();
1276
+ const rootId = scheduler3.getJobRootId(id);
1277
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1278
+ },
1262
1279
  /**
1263
1280
  * Pause this job (set enabled=false).
1264
1281
  * Job remains registered but won't run.
@@ -1676,13 +1693,19 @@ function addTail(callback) {
1676
1693
  return getScheduler().onIdle(callback);
1677
1694
  }
1678
1695
  function invalidate(state, frames = 1, stackFrames = false) {
1679
- getScheduler().invalidate(frames, stackFrames);
1696
+ const rootId = state?.internal?.rootId;
1697
+ const scheduler = getScheduler();
1698
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1699
+ else scheduler.invalidate(frames, stackFrames);
1680
1700
  }
1681
- function advance(timestamp) {
1682
- getScheduler().step(timestamp);
1701
+ function advance(timestamp, runGlobalEffects, state) {
1702
+ const rootId = state?.internal?.rootId;
1703
+ const scheduler = getScheduler();
1704
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1705
+ else scheduler.step(timestamp);
1683
1706
  }
1684
1707
 
1685
- const version = "10.0.0-alpha.3";
1708
+ const version = "10.0.0-alpha.4";
1686
1709
  const packageData = {
1687
1710
  version: version};
1688
1711
 
@@ -14198,9 +14221,8 @@ function createRoot(canvas) {
14198
14221
  if (!renderer.hasInitialized?.()) {
14199
14222
  const size2 = computeInitialSize(canvas, propsSize);
14200
14223
  if (size2.width > 0 && size2.height > 0) {
14201
- const pixelRatio = calculateDpr(dpr);
14202
- canvas.width = size2.width * pixelRatio;
14203
- canvas.height = size2.height * pixelRatio;
14224
+ renderer.setPixelRatio(calculateDpr(dpr));
14225
+ renderer.setSize(size2.width, size2.height, false);
14204
14226
  }
14205
14227
  await renderer.init();
14206
14228
  }
@@ -14209,7 +14231,7 @@ function createRoot(canvas) {
14209
14231
  state.internal.actualRenderer = renderer;
14210
14232
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14211
14233
  if (canvasId && !state.internal.isSecondary) {
14212
- const canvasTarget = new CanvasTarget(canvas);
14234
+ const canvasTarget = renderer.getCanvasTarget?.();
14213
14235
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14214
14236
  state.set((prev) => ({
14215
14237
  internal: {
@@ -14323,7 +14345,7 @@ function createRoot(canvas) {
14323
14345
  const handleXRFrame = (timestamp, _frame) => {
14324
14346
  const state2 = store.getState();
14325
14347
  if (state2.frameloop === "never") return;
14326
- advance(timestamp);
14348
+ advance(timestamp, true, state2);
14327
14349
  };
14328
14350
  const actualRenderer = state.internal.actualRenderer;
14329
14351
  const handleSessionChange = () => {
@@ -14331,7 +14353,7 @@ function createRoot(canvas) {
14331
14353
  const renderer2 = state2.internal.actualRenderer;
14332
14354
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14333
14355
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14334
- if (!renderer2.xr.isPresenting) invalidate();
14356
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14335
14357
  };
14336
14358
  const xr = {
14337
14359
  connect() {
@@ -14413,14 +14435,19 @@ function createRoot(canvas) {
14413
14435
  const newRootId = canvasId || scheduler.generateRootId();
14414
14436
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14415
14437
  getState: () => store.getState(),
14416
- onError: (err) => store.getState().setError(err)
14438
+ onError: (err) => store.getState().setError(err),
14439
+ frameloop: store.getState().frameloop,
14440
+ before: schedulerConfig?.before,
14441
+ after: schedulerConfig?.after,
14442
+ order: schedulerConfig?.order
14417
14443
  });
14418
14444
  const unregisterCanvasTarget = scheduler.register(
14419
14445
  () => {
14420
14446
  const state2 = store.getState();
14421
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14447
+ const canvasTarget = state2.internal.canvasTarget;
14448
+ if (canvasTarget) {
14422
14449
  const renderer2 = state2.internal.actualRenderer;
14423
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14450
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14424
14451
  if (state2.internal.canvasTargetSizeDirty) {
14425
14452
  state2.internal.canvasTargetSizeDirty = false;
14426
14453
  renderer2.backend?.updateSize?.();
@@ -14493,8 +14520,7 @@ function createRoot(canvas) {
14493
14520
  phase: "render",
14494
14521
  system: true,
14495
14522
  // Internal flag: this is a system job, not user-controlled
14496
- // Apply scheduler config for render ordering and rate limiting
14497
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14523
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14498
14524
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14499
14525
  }
14500
14526
  );
@@ -14513,8 +14539,21 @@ function createRoot(canvas) {
14513
14539
  scheduler
14514
14540
  }
14515
14541
  }));
14542
+ } else {
14543
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14544
+ if (constraintsChanged) {
14545
+ scheduler.setRootConstraints(rootId, {
14546
+ before: schedulerConfig?.before,
14547
+ after: schedulerConfig?.after
14548
+ });
14549
+ }
14550
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14551
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14552
+ }
14516
14553
  }
14517
- scheduler.frameloop = frameloop;
14554
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14555
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14556
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14518
14557
  onCreated = onCreatedCallback;
14519
14558
  configured = true;
14520
14559
  resolve();
@@ -14556,18 +14595,23 @@ function unmountComponentAtNode(canvas, callback) {
14556
14595
  const fiber = root?.fiber;
14557
14596
  if (fiber) {
14558
14597
  const state = root?.store.getState();
14559
- if (state) state.internal.active = false;
14598
+ if (state) {
14599
+ state.internal.active = false;
14600
+ const unregisterRoot = state.internal.unregisterRoot;
14601
+ if (unregisterRoot) {
14602
+ unregisterRoot();
14603
+ state.internal.unregisterRoot = void 0;
14604
+ }
14605
+ }
14560
14606
  reconciler.updateContainer(null, fiber, null, () => {
14561
14607
  if (state) {
14562
14608
  setTimeout(() => {
14563
14609
  try {
14564
14610
  const renderer = state.internal.actualRenderer;
14565
- const unregisterRoot = state.internal.unregisterRoot;
14566
- if (unregisterRoot) unregisterRoot();
14567
14611
  const unregisterPrimary = state.internal.unregisterPrimary;
14568
14612
  if (unregisterPrimary) unregisterPrimary();
14569
14613
  const canvasTarget = state.internal.canvasTarget;
14570
- if (canvasTarget?.dispose) canvasTarget.dispose();
14614
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14571
14615
  state.events.disconnect?.();
14572
14616
  cleanupHelperGroup(root.store);
14573
14617
  if (state.isLegacy && renderer) {
@@ -15108,9 +15152,9 @@ function CanvasImpl({
15108
15152
  React.useMemo(() => extend(THREE), []);
15109
15153
  const Bridge = useBridge();
15110
15154
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15111
- const hasInitialSizeRef = React.useRef(false);
15155
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15112
15156
  const measureConfig = React.useMemo(() => {
15113
- if (!hasInitialSizeRef.current) {
15157
+ if (!hasInitialSize) {
15114
15158
  return {
15115
15159
  ...resize,
15116
15160
  scroll: resize?.scroll ?? true,
@@ -15122,7 +15166,7 @@ function CanvasImpl({
15122
15166
  debounce: { scroll: 50, resize: 0 },
15123
15167
  ...resize
15124
15168
  };
15125
- }, [resize, hasInitialSizeRef.current]);
15169
+ }, [hasInitialSize, resize]);
15126
15170
  const [containerRef, containerRect] = useMeasure(measureConfig);
15127
15171
  const effectiveSize = React.useMemo(() => {
15128
15172
  let w = width ?? containerRect.width;
@@ -15138,9 +15182,7 @@ function CanvasImpl({
15138
15182
  left: containerRect.left
15139
15183
  };
15140
15184
  }, [width, height, containerRect, forceEven]);
15141
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15142
- hasInitialSizeRef.current = true;
15143
- }
15185
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15144
15186
  const canvasRef = React.useRef(null);
15145
15187
  const divRef = React.useRef(null);
15146
15188
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15254,6 +15296,7 @@ function CanvasImpl({
15254
15296
  const canvas = canvasRef.current;
15255
15297
  if (canvas) {
15256
15298
  return () => {
15299
+ if (canvas.isConnected) return;
15257
15300
  unmountComponentAtNode(canvas);
15258
15301
  root.current = null;
15259
15302
  };
@@ -15998,7 +16041,7 @@ function useRenderPipeline(mainCB, setupCB) {
15998
16041
  renderPipeline: null,
15999
16042
  passes: {}
16000
16043
  });
16001
- scenePassCacheRef.current?.scenePass?.dispose?.();
16044
+ scenePassCacheRef.current?.scenePass.dispose();
16002
16045
  callbacksRanRef.current = false;
16003
16046
  scenePassCacheRef.current = null;
16004
16047
  forceScenePassRebuildRef.current = false;
@@ -16013,6 +16056,9 @@ function useRenderPipeline(mainCB, setupCB) {
16013
16056
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16014
16057
  }
16015
16058
  if (!renderer || !scene || !camera) return;
16059
+ if (!("isWebGPURenderer" in renderer)) {
16060
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16061
+ }
16016
16062
  const state = store.getState();
16017
16063
  const set = store.setState;
16018
16064
  try {
@@ -16044,7 +16090,12 @@ function useRenderPipeline(mainCB, setupCB) {
16044
16090
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16045
16091
  if (shouldRunCallbacks) {
16046
16092
  if (setupCBRef.current) {
16047
- const freshState = store.getState();
16093
+ const freshState = Object.assign({}, store.getState(), {
16094
+ renderPipeline: pipeline,
16095
+ passes: { ...currentPasses, scenePass },
16096
+ renderer,
16097
+ isLegacy: false
16098
+ });
16048
16099
  const setupResult = setupCBRef.current(freshState);
16049
16100
  if (setupResult && typeof setupResult === "object") {
16050
16101
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16052,7 +16103,12 @@ function useRenderPipeline(mainCB, setupCB) {
16052
16103
  }
16053
16104
  }
16054
16105
  if (mainCBRef.current) {
16055
- const freshState = store.getState();
16106
+ const freshState = Object.assign({}, store.getState(), {
16107
+ renderPipeline: pipeline,
16108
+ passes: { ...currentPasses, scenePass },
16109
+ renderer,
16110
+ isLegacy: false
16111
+ });
16056
16112
  const mainResult = mainCBRef.current(freshState);
16057
16113
  if (mainResult && typeof mainResult === "object") {
16058
16114
  currentPasses = { ...currentPasses, ...mainResult };
@@ -16063,22 +16119,16 @@ function useRenderPipeline(mainCB, setupCB) {
16063
16119
  callbacksRanRef.current = true;
16064
16120
  }
16065
16121
  if (outputNodeChanged) pipeline.needsUpdate = true;
16066
- if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose?.();
16122
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16067
16123
  } catch (error) {
16068
16124
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16069
16125
  }
16070
16126
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16071
16127
  const passes = useThree$1((s) => s.passes);
16072
16128
  const renderPipeline = useThree$1((s) => s.renderPipeline);
16073
- return {
16074
- passes,
16075
- renderPipeline,
16076
- clearPasses,
16077
- reset,
16078
- rebuild,
16079
- // isReady indicates if RenderPipeline is configured and ready for rendering
16080
- isReady: renderPipeline !== null
16081
- };
16129
+ const actions = { passes, clearPasses, reset, rebuild };
16130
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16131
+ return { ...actions, isReady: false, renderPipeline: null };
16082
16132
  }
16083
16133
 
16084
16134
  extend(THREE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/fiber",
3
- "version": "10.0.0-canary.0f7fc9b",
3
+ "version": "10.0.0-canary.0fc81c2",
4
4
  "description": "A React renderer for Threejs",
5
5
  "keywords": [
6
6
  "react",
@@ -58,7 +58,7 @@
58
58
  "dependencies": {
59
59
  "@babel/runtime": "^7.28.6",
60
60
  "@monogrid/gainmap-js": "^3.4.0",
61
- "@pmndrs/scheduler": "^0.1.0",
61
+ "@pmndrs/scheduler": "^0.2.0",
62
62
  "dequal": "^2.0.3",
63
63
  "its-fine": "^2.0.0",
64
64
  "react-use-measure": "^2.1.7",