@react-three/fiber 10.0.0-canary.ac30da2 → 10.0.0-canary.af75fd4

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);
@@ -15999,7 +16041,7 @@ function useRenderPipeline(mainCB, setupCB) {
15999
16041
  renderPipeline: null,
16000
16042
  passes: {}
16001
16043
  });
16002
- scenePassCacheRef.current?.scenePass?.dispose?.();
16044
+ scenePassCacheRef.current?.scenePass.dispose();
16003
16045
  callbacksRanRef.current = false;
16004
16046
  scenePassCacheRef.current = null;
16005
16047
  forceScenePassRebuildRef.current = false;
@@ -16014,6 +16056,9 @@ function useRenderPipeline(mainCB, setupCB) {
16014
16056
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16015
16057
  }
16016
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
+ }
16017
16062
  const state = store.getState();
16018
16063
  const set = store.setState;
16019
16064
  try {
@@ -16045,7 +16090,12 @@ function useRenderPipeline(mainCB, setupCB) {
16045
16090
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16046
16091
  if (shouldRunCallbacks) {
16047
16092
  if (setupCBRef.current) {
16048
- const freshState = store.getState();
16093
+ const freshState = Object.assign({}, store.getState(), {
16094
+ renderPipeline: pipeline,
16095
+ passes: { ...currentPasses, scenePass },
16096
+ renderer,
16097
+ isLegacy: false
16098
+ });
16049
16099
  const setupResult = setupCBRef.current(freshState);
16050
16100
  if (setupResult && typeof setupResult === "object") {
16051
16101
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16053,7 +16103,12 @@ function useRenderPipeline(mainCB, setupCB) {
16053
16103
  }
16054
16104
  }
16055
16105
  if (mainCBRef.current) {
16056
- const freshState = store.getState();
16106
+ const freshState = Object.assign({}, store.getState(), {
16107
+ renderPipeline: pipeline,
16108
+ passes: { ...currentPasses, scenePass },
16109
+ renderer,
16110
+ isLegacy: false
16111
+ });
16057
16112
  const mainResult = mainCBRef.current(freshState);
16058
16113
  if (mainResult && typeof mainResult === "object") {
16059
16114
  currentPasses = { ...currentPasses, ...mainResult };
@@ -16064,22 +16119,16 @@ function useRenderPipeline(mainCB, setupCB) {
16064
16119
  callbacksRanRef.current = true;
16065
16120
  }
16066
16121
  if (outputNodeChanged) pipeline.needsUpdate = true;
16067
- if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose?.();
16122
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16068
16123
  } catch (error) {
16069
16124
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16070
16125
  }
16071
16126
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16072
16127
  const passes = useThree$1((s) => s.passes);
16073
16128
  const renderPipeline = useThree$1((s) => s.renderPipeline);
16074
- return {
16075
- passes,
16076
- renderPipeline,
16077
- clearPasses,
16078
- reset,
16079
- rebuild,
16080
- // isReady indicates if RenderPipeline is configured and ready for rendering
16081
- isReady: renderPipeline !== null
16082
- };
16129
+ const actions = { passes, clearPasses, reset, rebuild };
16130
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16131
+ return { ...actions, isReady: false, renderPipeline: null };
16083
16132
  }
16084
16133
 
16085
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.ac30da2",
3
+ "version": "10.0.0-canary.af75fd4",
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",