@react-three/fiber 10.0.0-canary.d6fdf72 → 10.0.0-canary.f046a54
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.
- package/dist/index.cjs +230 -1195
- package/dist/index.d.cts +25 -720
- package/dist/index.d.mts +25 -720
- package/dist/index.d.ts +25 -720
- package/dist/index.mjs +203 -1167
- package/dist/legacy.cjs +185 -1150
- package/dist/legacy.d.cts +25 -720
- package/dist/legacy.d.mts +25 -720
- package/dist/legacy.d.ts +25 -720
- package/dist/legacy.mjs +158 -1122
- package/dist/webgpu/index.cjs +261 -1208
- package/dist/webgpu/index.d.cts +25 -720
- package/dist/webgpu/index.d.mts +25 -720
- package/dist/webgpu/index.d.ts +25 -720
- package/dist/webgpu/index.mjs +234 -1180
- package/package.json +2 -1
- package/readme.md +2 -2
package/dist/webgpu/index.cjs
CHANGED
|
@@ -14,6 +14,7 @@ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js'
|
|
|
14
14
|
const gainmapJs = require('@monogrid/gainmap-js');
|
|
15
15
|
const Tb = require('scheduler');
|
|
16
16
|
const traditional = require('zustand/traditional');
|
|
17
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
17
18
|
const suspendReact = require('suspend-react');
|
|
18
19
|
const lite = require('dequal/lite');
|
|
19
20
|
require('zustand/shallow');
|
|
@@ -456,9 +457,9 @@ function Environment(props) {
|
|
|
456
457
|
return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
|
|
457
458
|
}
|
|
458
459
|
|
|
459
|
-
var __defProp$
|
|
460
|
-
var __defNormalProp$
|
|
461
|
-
var __publicField$
|
|
460
|
+
var __defProp$1 = Object.defineProperty;
|
|
461
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
462
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
462
463
|
const act = React__namespace["act"];
|
|
463
464
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
464
465
|
function useMutableCallback(fn) {
|
|
@@ -490,7 +491,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
490
491
|
return _a = class extends React__namespace.Component {
|
|
491
492
|
constructor() {
|
|
492
493
|
super(...arguments);
|
|
493
|
-
__publicField$
|
|
494
|
+
__publicField$1(this, "state", { error: false });
|
|
494
495
|
}
|
|
495
496
|
componentDidCatch(err) {
|
|
496
497
|
this.props.set(err);
|
|
@@ -498,7 +499,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
498
499
|
render() {
|
|
499
500
|
return this.state.error ? null : this.props.children;
|
|
500
501
|
}
|
|
501
|
-
}, __publicField$
|
|
502
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
502
503
|
})();
|
|
503
504
|
|
|
504
505
|
const is = {
|
|
@@ -1019,6 +1020,19 @@ function applyProps(object, props) {
|
|
|
1019
1020
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
1020
1021
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
1021
1022
|
else target.set(value);
|
|
1023
|
+
} else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1024
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1025
|
+
const uniforms = root.uniforms;
|
|
1026
|
+
const nextUniforms = value;
|
|
1027
|
+
for (const name in nextUniforms) {
|
|
1028
|
+
const uniform = nextUniforms[name];
|
|
1029
|
+
const targetUniform = uniforms[name];
|
|
1030
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1031
|
+
else {
|
|
1032
|
+
const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
|
|
1033
|
+
uniforms[name] = nextUniform;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1022
1036
|
} else {
|
|
1023
1037
|
root[key] = value;
|
|
1024
1038
|
if (key.endsWith("Node") && root.isMaterial) {
|
|
@@ -1658,1036 +1672,12 @@ function notifyAlpha({ message, link }) {
|
|
|
1658
1672
|
}
|
|
1659
1673
|
}
|
|
1660
1674
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
/** Ordered list of phase nodes */
|
|
1668
|
-
__publicField$2(this, "phases", []);
|
|
1669
|
-
/** Quick lookup by name */
|
|
1670
|
-
__publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1671
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1672
|
-
__publicField$2(this, "orderedNamesCache", null);
|
|
1673
|
-
this.initializeDefaultPhases();
|
|
1674
|
-
}
|
|
1675
|
-
//* Initialization --------------------------------
|
|
1676
|
-
initializeDefaultPhases() {
|
|
1677
|
-
for (const name of DEFAULT_PHASES) {
|
|
1678
|
-
const node = { name, isAutoGenerated: false };
|
|
1679
|
-
this.phases.push(node);
|
|
1680
|
-
this.phaseMap.set(name, node);
|
|
1681
|
-
}
|
|
1682
|
-
this.invalidateCache();
|
|
1683
|
-
}
|
|
1684
|
-
//* Public API --------------------------------
|
|
1685
|
-
/**
|
|
1686
|
-
* Add a named phase to the graph
|
|
1687
|
-
* @param name - Phase name (must be unique)
|
|
1688
|
-
* @param options - Position options (before or after another phase)
|
|
1689
|
-
*/
|
|
1690
|
-
addPhase(name, options = {}) {
|
|
1691
|
-
if (this.phaseMap.has(name)) {
|
|
1692
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
const { before, after } = options;
|
|
1696
|
-
const node = { name, isAutoGenerated: false };
|
|
1697
|
-
let insertIndex = this.phases.length;
|
|
1698
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1699
|
-
if (targetIndex !== -1) {
|
|
1700
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1701
|
-
} else {
|
|
1702
|
-
const constraintType = before ? "before" : "after";
|
|
1703
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1704
|
-
}
|
|
1705
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1706
|
-
this.phaseMap.set(name, node);
|
|
1707
|
-
this.invalidateCache();
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Get ordered list of phase names
|
|
1711
|
-
*/
|
|
1712
|
-
getOrderedPhases() {
|
|
1713
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1714
|
-
return this.orderedNamesCache;
|
|
1715
|
-
}
|
|
1716
|
-
/**
|
|
1717
|
-
* Check if a phase exists
|
|
1718
|
-
*/
|
|
1719
|
-
hasPhase(name) {
|
|
1720
|
-
return this.phaseMap.has(name);
|
|
1721
|
-
}
|
|
1722
|
-
/**
|
|
1723
|
-
* Get the index of a phase (-1 if not found)
|
|
1724
|
-
*/
|
|
1725
|
-
getPhaseIndex(name) {
|
|
1726
|
-
if (!name) return -1;
|
|
1727
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1728
|
-
}
|
|
1729
|
-
/**
|
|
1730
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1731
|
-
* Used for resolving before/after constraints.
|
|
1732
|
-
*
|
|
1733
|
-
* @param name - The phase name to ensure exists
|
|
1734
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1735
|
-
*/
|
|
1736
|
-
ensurePhase(name) {
|
|
1737
|
-
if (this.phaseMap.has(name)) return name;
|
|
1738
|
-
const node = { name, isAutoGenerated: true };
|
|
1739
|
-
this.phases.push(node);
|
|
1740
|
-
this.phaseMap.set(name, node);
|
|
1741
|
-
this.invalidateCache();
|
|
1742
|
-
return name;
|
|
1743
|
-
}
|
|
1744
|
-
/**
|
|
1745
|
-
* Resolve where a job with before/after constraints should go.
|
|
1746
|
-
* Creates auto-generated phases if needed.
|
|
1747
|
-
*
|
|
1748
|
-
* @param before - Phase(s) to run before
|
|
1749
|
-
* @param after - Phase(s) to run after
|
|
1750
|
-
* @returns The resolved phase name
|
|
1751
|
-
*/
|
|
1752
|
-
resolveConstraintPhase(before, after) {
|
|
1753
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1754
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1755
|
-
if (beforeArr.length > 0) {
|
|
1756
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1757
|
-
}
|
|
1758
|
-
if (afterArr.length > 0) {
|
|
1759
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1760
|
-
}
|
|
1761
|
-
return "update";
|
|
1762
|
-
}
|
|
1763
|
-
/**
|
|
1764
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1765
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1766
|
-
*
|
|
1767
|
-
* @param target - The target phase name to position relative to
|
|
1768
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1769
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1770
|
-
* @returns The auto-generated phase name
|
|
1771
|
-
*/
|
|
1772
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1773
|
-
const autoName = `${prefix}:${target}`;
|
|
1774
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1775
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1776
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1777
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1778
|
-
else this.phases.push(node);
|
|
1779
|
-
this.phaseMap.set(autoName, node);
|
|
1780
|
-
this.invalidateCache();
|
|
1781
|
-
return autoName;
|
|
1782
|
-
}
|
|
1783
|
-
// Internal --------------------------------
|
|
1784
|
-
invalidateCache() {
|
|
1785
|
-
this.orderedNamesCache = null;
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1790
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1791
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1792
|
-
for (const phase of orderedPhases) {
|
|
1793
|
-
buckets.set(phase, []);
|
|
1794
|
-
}
|
|
1795
|
-
for (const job of jobs.values()) {
|
|
1796
|
-
if (!job.enabled) continue;
|
|
1797
|
-
let bucket = buckets.get(job.phase);
|
|
1798
|
-
if (!bucket) {
|
|
1799
|
-
bucket = [];
|
|
1800
|
-
buckets.set(job.phase, bucket);
|
|
1801
|
-
}
|
|
1802
|
-
bucket.push(job);
|
|
1803
|
-
}
|
|
1804
|
-
const sortedBuckets = [];
|
|
1805
|
-
for (const phase of orderedPhases) {
|
|
1806
|
-
const bucket = buckets.get(phase);
|
|
1807
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1808
|
-
bucket.sort((a, b) => {
|
|
1809
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1810
|
-
return a.index - b.index;
|
|
1811
|
-
});
|
|
1812
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1813
|
-
}
|
|
1814
|
-
for (const [phase, bucket] of buckets) {
|
|
1815
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1816
|
-
bucket.sort((a, b) => {
|
|
1817
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1818
|
-
return a.index - b.index;
|
|
1819
|
-
});
|
|
1820
|
-
sortedBuckets.push(bucket);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
return sortedBuckets.flat();
|
|
1824
|
-
}
|
|
1825
|
-
function hasCrossJobConstraints(bucket) {
|
|
1826
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1827
|
-
for (const job of bucket) {
|
|
1828
|
-
for (const ref of job.before) {
|
|
1829
|
-
if (jobIds.has(ref)) return true;
|
|
1830
|
-
}
|
|
1831
|
-
for (const ref of job.after) {
|
|
1832
|
-
if (jobIds.has(ref)) return true;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
return false;
|
|
1836
|
-
}
|
|
1837
|
-
function topologicalSort(jobs) {
|
|
1838
|
-
const n = jobs.length;
|
|
1839
|
-
if (n <= 1) return jobs;
|
|
1840
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1841
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1842
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1843
|
-
for (const job of jobs) {
|
|
1844
|
-
jobMap.set(job.id, job);
|
|
1845
|
-
inDegree.set(job.id, 0);
|
|
1846
|
-
adjacency.set(job.id, []);
|
|
1847
|
-
}
|
|
1848
|
-
for (const job of jobs) {
|
|
1849
|
-
for (const ref of job.before) {
|
|
1850
|
-
if (jobMap.has(ref)) {
|
|
1851
|
-
adjacency.get(job.id).push(ref);
|
|
1852
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
for (const ref of job.after) {
|
|
1856
|
-
if (jobMap.has(ref)) {
|
|
1857
|
-
adjacency.get(ref).push(job.id);
|
|
1858
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
const queue = [];
|
|
1863
|
-
for (const job of jobs) {
|
|
1864
|
-
if (inDegree.get(job.id) === 0) {
|
|
1865
|
-
queue.push(job);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
queue.sort((a, b) => {
|
|
1869
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1870
|
-
return a.index - b.index;
|
|
1871
|
-
});
|
|
1872
|
-
const result = [];
|
|
1873
|
-
while (queue.length > 0) {
|
|
1874
|
-
const job = queue.shift();
|
|
1875
|
-
result.push(job);
|
|
1876
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1877
|
-
for (const neighborId of neighbors) {
|
|
1878
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1879
|
-
inDegree.set(neighborId, newDegree);
|
|
1880
|
-
if (newDegree === 0) {
|
|
1881
|
-
const neighbor = jobMap.get(neighborId);
|
|
1882
|
-
insertSorted(queue, neighbor);
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
if (result.length !== n) {
|
|
1887
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1888
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1889
|
-
for (const job of jobs) {
|
|
1890
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
return result;
|
|
1894
|
-
}
|
|
1895
|
-
function insertSorted(arr, job) {
|
|
1896
|
-
let i = 0;
|
|
1897
|
-
while (i < arr.length) {
|
|
1898
|
-
const cmp = arr[i];
|
|
1899
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1900
|
-
break;
|
|
1901
|
-
}
|
|
1902
|
-
i++;
|
|
1903
|
-
}
|
|
1904
|
-
arr.splice(i, 0, job);
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
function shouldRun(job, now) {
|
|
1908
|
-
if (!job.enabled) return false;
|
|
1909
|
-
if (!job.fps) return true;
|
|
1910
|
-
const minInterval = 1e3 / job.fps;
|
|
1911
|
-
const lastRun = job.lastRun ?? 0;
|
|
1912
|
-
const elapsed = now - lastRun;
|
|
1913
|
-
if (elapsed < minInterval - 1) return false;
|
|
1914
|
-
if (job.drop) {
|
|
1915
|
-
job.lastRun = now;
|
|
1916
|
-
} else {
|
|
1917
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1918
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1919
|
-
if (job.lastRun < now - minInterval) {
|
|
1920
|
-
job.lastRun = now;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
return true;
|
|
1924
|
-
}
|
|
1925
|
-
function resetJobTiming(job) {
|
|
1926
|
-
job.lastRun = void 0;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
var __defProp$1 = Object.defineProperty;
|
|
1930
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1931
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1932
|
-
const hmrData = (() => {
|
|
1933
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1934
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1935
|
-
try {
|
|
1936
|
-
return (0, eval)("import.meta.hot");
|
|
1937
|
-
} catch {
|
|
1938
|
-
return void 0;
|
|
1939
|
-
}
|
|
1940
|
-
})();
|
|
1941
|
-
const _Scheduler = class _Scheduler {
|
|
1942
|
-
//* Constructor ================================
|
|
1943
|
-
constructor() {
|
|
1944
|
-
//* Critical State ================================
|
|
1945
|
-
__publicField$1(this, "roots", /* @__PURE__ */ new Map());
|
|
1946
|
-
__publicField$1(this, "phaseGraph");
|
|
1947
|
-
__publicField$1(this, "loopState", {
|
|
1948
|
-
running: false,
|
|
1949
|
-
rafHandle: null,
|
|
1950
|
-
lastTime: null,
|
|
1951
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1952
|
-
frameCount: 0,
|
|
1953
|
-
elapsedTime: 0,
|
|
1954
|
-
createdAt: performance.now()
|
|
1955
|
-
});
|
|
1956
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1957
|
-
//* Private State ================================
|
|
1958
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1959
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1960
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1961
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1962
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1963
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1964
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1965
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1966
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1967
|
-
//* Independent Mode & Error Handling State ================================
|
|
1968
|
-
__publicField$1(this, "_independent", false);
|
|
1969
|
-
__publicField$1(this, "errorHandler", null);
|
|
1970
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1971
|
-
//* Core Loop Execution Methods ================================
|
|
1972
|
-
/**
|
|
1973
|
-
* Main RAF loop callback.
|
|
1974
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1975
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1976
|
-
* @returns {void}
|
|
1977
|
-
* @private
|
|
1978
|
-
*/
|
|
1979
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1980
|
-
if (!this.loopState.running) return;
|
|
1981
|
-
this.executeFrame(timestamp);
|
|
1982
|
-
if (this._frameloop === "demand") {
|
|
1983
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1984
|
-
if (this.pendingFrames === 0) {
|
|
1985
|
-
this.notifyIdle(timestamp);
|
|
1986
|
-
return this.stop();
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1990
|
-
});
|
|
1991
|
-
this.phaseGraph = new PhaseGraph();
|
|
1992
|
-
}
|
|
1993
|
-
static get instance() {
|
|
1994
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1995
|
-
}
|
|
1996
|
-
static set instance(value) {
|
|
1997
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
2001
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
2002
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
2003
|
-
*/
|
|
2004
|
-
static get() {
|
|
2005
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
2006
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
2007
|
-
}
|
|
2008
|
-
if (!_Scheduler.instance) {
|
|
2009
|
-
_Scheduler.instance = new _Scheduler();
|
|
2010
|
-
if (hmrData?.data) {
|
|
2011
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
return _Scheduler.instance;
|
|
2015
|
-
}
|
|
2016
|
-
/**
|
|
2017
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
2018
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
2019
|
-
* @returns {void}
|
|
2020
|
-
*/
|
|
2021
|
-
static reset() {
|
|
2022
|
-
if (_Scheduler.instance) {
|
|
2023
|
-
_Scheduler.instance.stop();
|
|
2024
|
-
_Scheduler.instance = null;
|
|
2025
|
-
}
|
|
2026
|
-
if (hmrData?.data) {
|
|
2027
|
-
hmrData.data.scheduler = null;
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
//* Getters & Setters ================================
|
|
2031
|
-
get phases() {
|
|
2032
|
-
return this.phaseGraph.getOrderedPhases();
|
|
2033
|
-
}
|
|
2034
|
-
get frameloop() {
|
|
2035
|
-
return this._frameloop;
|
|
2036
|
-
}
|
|
2037
|
-
set frameloop(mode) {
|
|
2038
|
-
if (this._frameloop === mode) return;
|
|
2039
|
-
const wasAlways = this._frameloop === "always";
|
|
2040
|
-
this._frameloop = mode;
|
|
2041
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
2042
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
2043
|
-
}
|
|
2044
|
-
get isRunning() {
|
|
2045
|
-
return this.loopState.running;
|
|
2046
|
-
}
|
|
2047
|
-
get isReady() {
|
|
2048
|
-
return this.roots.size > 0;
|
|
2049
|
-
}
|
|
2050
|
-
get independent() {
|
|
2051
|
-
return this._independent;
|
|
2052
|
-
}
|
|
2053
|
-
set independent(value) {
|
|
2054
|
-
this._independent = value;
|
|
2055
|
-
if (value) this.ensureDefaultRoot();
|
|
2056
|
-
}
|
|
2057
|
-
//* Root Management Methods ================================
|
|
2058
|
-
/**
|
|
2059
|
-
* Register a root (Canvas) with the scheduler.
|
|
2060
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
2061
|
-
* @param {string} id - Unique identifier for this root
|
|
2062
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
2063
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
2064
|
-
*/
|
|
2065
|
-
registerRoot(id, options = {}) {
|
|
2066
|
-
if (this.roots.has(id)) {
|
|
2067
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
2068
|
-
return () => this.unregisterRoot(id);
|
|
2069
|
-
}
|
|
2070
|
-
const entry = {
|
|
2071
|
-
id,
|
|
2072
|
-
getState: options.getState ?? (() => ({})),
|
|
2073
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
2074
|
-
sortedJobs: [],
|
|
2075
|
-
needsRebuild: false
|
|
2076
|
-
};
|
|
2077
|
-
if (options.onError) {
|
|
2078
|
-
this.errorHandler = options.onError;
|
|
2079
|
-
}
|
|
2080
|
-
this.roots.set(id, entry);
|
|
2081
|
-
if (this.roots.size === 1) {
|
|
2082
|
-
this.notifyRootReady();
|
|
2083
|
-
if (this._frameloop === "always") this.start();
|
|
2084
|
-
}
|
|
2085
|
-
return () => this.unregisterRoot(id);
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Unregister a root from the scheduler.
|
|
2089
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
2090
|
-
* The last root to unregister stops the RAF loop.
|
|
2091
|
-
* @param {string} id - The root ID to unregister
|
|
2092
|
-
* @returns {void}
|
|
2093
|
-
*/
|
|
2094
|
-
unregisterRoot(id) {
|
|
2095
|
-
const root = this.roots.get(id);
|
|
2096
|
-
if (!root) return;
|
|
2097
|
-
for (const jobId of root.jobs.keys()) {
|
|
2098
|
-
this.jobStateListeners.delete(jobId);
|
|
2099
|
-
}
|
|
2100
|
-
this.roots.delete(id);
|
|
2101
|
-
if (this.roots.size === 0) {
|
|
2102
|
-
this.stop();
|
|
2103
|
-
this.errorHandler = null;
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
/**
|
|
2107
|
-
* Subscribe to be notified when a root becomes available.
|
|
2108
|
-
* Fires immediately if a root already exists.
|
|
2109
|
-
* @param {() => void} callback - Function called when first root registers
|
|
2110
|
-
* @returns {() => void} Unsubscribe function
|
|
2111
|
-
*/
|
|
2112
|
-
onRootReady(callback) {
|
|
2113
|
-
if (this.roots.size > 0) {
|
|
2114
|
-
callback();
|
|
2115
|
-
return () => {
|
|
2116
|
-
};
|
|
2117
|
-
}
|
|
2118
|
-
this.rootReadyCallbacks.add(callback);
|
|
2119
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
2120
|
-
}
|
|
2121
|
-
/**
|
|
2122
|
-
* Notify all registered root-ready callbacks.
|
|
2123
|
-
* Called when the first root registers.
|
|
2124
|
-
* @returns {void}
|
|
2125
|
-
* @private
|
|
2126
|
-
*/
|
|
2127
|
-
notifyRootReady() {
|
|
2128
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
2129
|
-
try {
|
|
2130
|
-
cb();
|
|
2131
|
-
} catch (error) {
|
|
2132
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
this.rootReadyCallbacks.clear();
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* Ensure a default root exists for independent mode.
|
|
2139
|
-
* Creates a minimal root with no state provider.
|
|
2140
|
-
* @returns {void}
|
|
2141
|
-
* @private
|
|
2142
|
-
*/
|
|
2143
|
-
ensureDefaultRoot() {
|
|
2144
|
-
if (!this.roots.has("__default__")) {
|
|
2145
|
-
this.registerRoot("__default__");
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Trigger error handling for job errors.
|
|
2150
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
2151
|
-
* @param {Error} error - The error to handle
|
|
2152
|
-
* @returns {void}
|
|
2153
|
-
*/
|
|
2154
|
-
triggerError(error) {
|
|
2155
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
2156
|
-
else console.error("[Scheduler]", error);
|
|
2157
|
-
}
|
|
2158
|
-
//* Phase Management Methods ================================
|
|
2159
|
-
/**
|
|
2160
|
-
* Add a named phase to the scheduler's execution order.
|
|
2161
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
2162
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
2163
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
2164
|
-
* @returns {void}
|
|
2165
|
-
* @example
|
|
2166
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
2167
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
2168
|
-
*/
|
|
2169
|
-
addPhase(name, options) {
|
|
2170
|
-
this.phaseGraph.addPhase(name, options);
|
|
2171
|
-
for (const root of this.roots.values()) {
|
|
2172
|
-
root.needsRebuild = true;
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
/**
|
|
2176
|
-
* Check if a phase exists in the scheduler.
|
|
2177
|
-
* @param {string} name - The phase name to check
|
|
2178
|
-
* @returns {boolean} True if the phase exists
|
|
2179
|
-
*/
|
|
2180
|
-
hasPhase(name) {
|
|
2181
|
-
return this.phaseGraph.hasPhase(name);
|
|
2182
|
-
}
|
|
2183
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
2184
|
-
/**
|
|
2185
|
-
* Register a global job that runs once per frame (not per-root).
|
|
2186
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
2187
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
2188
|
-
* @param {string} id - Unique identifier for this global job
|
|
2189
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
2190
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
2191
|
-
* @deprecated Use useFrame with phases instead
|
|
2192
|
-
*/
|
|
2193
|
-
registerGlobal(phase, id, callback) {
|
|
2194
|
-
const job = { id, callback };
|
|
2195
|
-
if (phase === "before") {
|
|
2196
|
-
this.globalBeforeJobs.set(id, job);
|
|
2197
|
-
} else {
|
|
2198
|
-
this.globalAfterJobs.set(id, job);
|
|
2199
|
-
}
|
|
2200
|
-
return () => {
|
|
2201
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
2202
|
-
else this.globalAfterJobs.delete(id);
|
|
2203
|
-
};
|
|
2204
|
-
}
|
|
2205
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
2206
|
-
/**
|
|
2207
|
-
* Register an idle callback that fires when the loop stops.
|
|
2208
|
-
* Used internally by deprecated addTail API.
|
|
2209
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
2210
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
2211
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
2212
|
-
*/
|
|
2213
|
-
onIdle(callback) {
|
|
2214
|
-
this.idleCallbacks.add(callback);
|
|
2215
|
-
return () => this.idleCallbacks.delete(callback);
|
|
2216
|
-
}
|
|
2217
|
-
/**
|
|
2218
|
-
* Notify all registered idle callbacks.
|
|
2219
|
-
* Called when the loop stops in demand mode.
|
|
2220
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
2221
|
-
* @returns {void}
|
|
2222
|
-
* @private
|
|
2223
|
-
*/
|
|
2224
|
-
notifyIdle(timestamp) {
|
|
2225
|
-
for (const cb of this.idleCallbacks) {
|
|
2226
|
-
try {
|
|
2227
|
-
cb(timestamp);
|
|
2228
|
-
} catch (error) {
|
|
2229
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
//* Job Registration & Management Methods ================================
|
|
2234
|
-
/**
|
|
2235
|
-
* Register a job (frame callback) with a specific root.
|
|
2236
|
-
* This is the core registration method used by useFrame internally.
|
|
2237
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
2238
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
2239
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
2240
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
2241
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
2242
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
2243
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
2244
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
2245
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
2246
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
2247
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
2248
|
-
*/
|
|
2249
|
-
register(callback, options = {}) {
|
|
2250
|
-
const rootId = options.rootId;
|
|
2251
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
2252
|
-
if (!root) {
|
|
2253
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
2254
|
-
return () => {
|
|
2255
|
-
};
|
|
2256
|
-
}
|
|
2257
|
-
const id = options.id ?? this.generateJobId();
|
|
2258
|
-
let phase = options.phase ?? "update";
|
|
2259
|
-
if (!options.phase && (options.before || options.after)) {
|
|
2260
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
2261
|
-
}
|
|
2262
|
-
const before = this.normalizeConstraints(options.before);
|
|
2263
|
-
const after = this.normalizeConstraints(options.after);
|
|
2264
|
-
const job = {
|
|
2265
|
-
id,
|
|
2266
|
-
callback,
|
|
2267
|
-
phase,
|
|
2268
|
-
before,
|
|
2269
|
-
after,
|
|
2270
|
-
priority: options.priority ?? 0,
|
|
2271
|
-
index: this.nextJobIndex++,
|
|
2272
|
-
fps: options.fps,
|
|
2273
|
-
drop: options.drop ?? true,
|
|
2274
|
-
enabled: options.enabled ?? true,
|
|
2275
|
-
system: options.system ?? false
|
|
2276
|
-
};
|
|
2277
|
-
if (root.jobs.has(id)) {
|
|
2278
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
2279
|
-
}
|
|
2280
|
-
root.jobs.set(id, job);
|
|
2281
|
-
root.needsRebuild = true;
|
|
2282
|
-
return () => this.unregister(id, root.id);
|
|
2283
|
-
}
|
|
2284
|
-
/**
|
|
2285
|
-
* Unregister a job by its ID.
|
|
2286
|
-
* Searches all roots if rootId is not provided.
|
|
2287
|
-
* @param {string} id - The job ID to unregister
|
|
2288
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
2289
|
-
* @returns {void}
|
|
2290
|
-
*/
|
|
2291
|
-
unregister(id, rootId) {
|
|
2292
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
2293
|
-
if (root?.jobs.delete(id)) {
|
|
2294
|
-
root.needsRebuild = true;
|
|
2295
|
-
this.jobStateListeners.delete(id);
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
/**
|
|
2299
|
-
* Update a job's options dynamically.
|
|
2300
|
-
* Searches all roots to find the job by ID.
|
|
2301
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2302
|
-
* @param {string} id - The job ID to update
|
|
2303
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2304
|
-
* @returns {void}
|
|
2305
|
-
*/
|
|
2306
|
-
updateJob(id, options) {
|
|
2307
|
-
let job;
|
|
2308
|
-
let root;
|
|
2309
|
-
for (const r of this.roots.values()) {
|
|
2310
|
-
job = r.jobs.get(id);
|
|
2311
|
-
if (job) {
|
|
2312
|
-
root = r;
|
|
2313
|
-
break;
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
if (!job || !root) return;
|
|
2317
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2318
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2319
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2320
|
-
if (options.enabled !== void 0) {
|
|
2321
|
-
const wasEnabled = job.enabled;
|
|
2322
|
-
job.enabled = options.enabled;
|
|
2323
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2324
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2325
|
-
}
|
|
2326
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2327
|
-
if (options.phase) job.phase = options.phase;
|
|
2328
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2329
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2330
|
-
root.needsRebuild = true;
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
//* Job State Management Methods ================================
|
|
2334
|
-
/**
|
|
2335
|
-
* Check if a job is currently paused (disabled).
|
|
2336
|
-
* @param {string} id - The job ID to check
|
|
2337
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2338
|
-
*/
|
|
2339
|
-
isJobPaused(id) {
|
|
2340
|
-
for (const root of this.roots.values()) {
|
|
2341
|
-
const job = root.jobs.get(id);
|
|
2342
|
-
if (job) return !job.enabled;
|
|
2343
|
-
}
|
|
2344
|
-
return false;
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Subscribe to state changes for a specific job.
|
|
2348
|
-
* Listener is called when job is paused or resumed.
|
|
2349
|
-
* @param {string} id - The job ID to subscribe to
|
|
2350
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2351
|
-
* @returns {() => void} Unsubscribe function
|
|
2352
|
-
*/
|
|
2353
|
-
subscribeJobState(id, listener) {
|
|
2354
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2355
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2356
|
-
}
|
|
2357
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2358
|
-
return () => {
|
|
2359
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2360
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2361
|
-
this.jobStateListeners.delete(id);
|
|
2362
|
-
}
|
|
2363
|
-
};
|
|
2364
|
-
}
|
|
2365
|
-
/**
|
|
2366
|
-
* Notify all listeners that a job's state has changed.
|
|
2367
|
-
* @param {string} id - The job ID that changed
|
|
2368
|
-
* @returns {void}
|
|
2369
|
-
* @private
|
|
2370
|
-
*/
|
|
2371
|
-
notifyJobStateChange(id) {
|
|
2372
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2373
|
-
}
|
|
2374
|
-
/**
|
|
2375
|
-
* Pause a job by ID (sets enabled=false).
|
|
2376
|
-
* Notifies any subscribed state listeners.
|
|
2377
|
-
* @param {string} id - The job ID to pause
|
|
2378
|
-
* @returns {void}
|
|
2379
|
-
*/
|
|
2380
|
-
pauseJob(id) {
|
|
2381
|
-
this.updateJob(id, { enabled: false });
|
|
2382
|
-
this.notifyJobStateChange(id);
|
|
2383
|
-
}
|
|
2384
|
-
/**
|
|
2385
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2386
|
-
* Resets job timing to prevent frame accumulation.
|
|
2387
|
-
* Notifies any subscribed state listeners.
|
|
2388
|
-
* @param {string} id - The job ID to resume
|
|
2389
|
-
* @returns {void}
|
|
2390
|
-
*/
|
|
2391
|
-
resumeJob(id) {
|
|
2392
|
-
this.updateJob(id, { enabled: true });
|
|
2393
|
-
this.notifyJobStateChange(id);
|
|
2394
|
-
}
|
|
2395
|
-
//* Frame Loop Control Methods ================================
|
|
2396
|
-
/**
|
|
2397
|
-
* Start the requestAnimationFrame loop.
|
|
2398
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2399
|
-
* No-op if already running.
|
|
2400
|
-
* @returns {void}
|
|
2401
|
-
*/
|
|
2402
|
-
start() {
|
|
2403
|
-
if (this.loopState.running) return;
|
|
2404
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2405
|
-
let adjustedCreated = 0;
|
|
2406
|
-
if (this.stoppedTime > 0) {
|
|
2407
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2408
|
-
this.stoppedTime = 0;
|
|
2409
|
-
}
|
|
2410
|
-
Object.assign(this.loopState, {
|
|
2411
|
-
running: true,
|
|
2412
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2413
|
-
lastTime: performance.now(),
|
|
2414
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2415
|
-
frameCount: 0,
|
|
2416
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2417
|
-
});
|
|
2418
|
-
}
|
|
2419
|
-
/**
|
|
2420
|
-
* Stop the requestAnimationFrame loop.
|
|
2421
|
-
* Cancels any pending RAF callback.
|
|
2422
|
-
* No-op if not running.
|
|
2423
|
-
* @returns {void}
|
|
2424
|
-
*/
|
|
2425
|
-
stop() {
|
|
2426
|
-
if (!this.loopState.running) return;
|
|
2427
|
-
this.loopState.running = false;
|
|
2428
|
-
if (this.loopState.rafHandle !== null) {
|
|
2429
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2430
|
-
this.loopState.rafHandle = null;
|
|
2431
|
-
}
|
|
2432
|
-
this.stoppedTime = performance.now();
|
|
2433
|
-
}
|
|
2434
|
-
/**
|
|
2435
|
-
* Request frames to be rendered in demand mode.
|
|
2436
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2437
|
-
* No-op if frameloop is not 'demand'.
|
|
2438
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2439
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2440
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2441
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2442
|
-
* @returns {void}
|
|
2443
|
-
* @example
|
|
2444
|
-
* // Request a single frame render
|
|
2445
|
-
* scheduler.invalidate();
|
|
2446
|
-
*
|
|
2447
|
-
* @example
|
|
2448
|
-
* // Request 5 frames (e.g., for animations)
|
|
2449
|
-
* scheduler.invalidate(5);
|
|
2450
|
-
*
|
|
2451
|
-
* @example
|
|
2452
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2453
|
-
* scheduler.invalidate(3, false);
|
|
2454
|
-
*
|
|
2455
|
-
* @example
|
|
2456
|
-
* // Add 2 more frames to existing pending count
|
|
2457
|
-
* scheduler.invalidate(2, true);
|
|
2458
|
-
*/
|
|
2459
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2460
|
-
if (this._frameloop !== "demand") return;
|
|
2461
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2462
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2463
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2464
|
-
}
|
|
2465
|
-
/**
|
|
2466
|
-
* Reset timing state for deterministic testing.
|
|
2467
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2468
|
-
* @returns {void}
|
|
2469
|
-
*/
|
|
2470
|
-
resetTiming() {
|
|
2471
|
-
this.loopState.lastTime = null;
|
|
2472
|
-
this.loopState.frameCount = 0;
|
|
2473
|
-
this.loopState.elapsedTime = 0;
|
|
2474
|
-
this.loopState.createdAt = performance.now();
|
|
2475
|
-
}
|
|
2476
|
-
//* Manual Stepping Methods ================================
|
|
2477
|
-
/**
|
|
2478
|
-
* Manually execute a single frame for all roots.
|
|
2479
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2480
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2481
|
-
* @returns {void}
|
|
2482
|
-
* @example
|
|
2483
|
-
* // Manual control mode
|
|
2484
|
-
* scheduler.frameloop = 'never';
|
|
2485
|
-
* scheduler.step(); // Execute one frame
|
|
2486
|
-
*/
|
|
2487
|
-
step(timestamp) {
|
|
2488
|
-
const now = timestamp ?? performance.now();
|
|
2489
|
-
this.executeFrame(now);
|
|
2490
|
-
}
|
|
2491
|
-
/**
|
|
2492
|
-
* Manually execute a single job by its ID.
|
|
2493
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2494
|
-
* @param {string} id - The job ID to step
|
|
2495
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2496
|
-
* @returns {void}
|
|
2497
|
-
*/
|
|
2498
|
-
stepJob(id, timestamp) {
|
|
2499
|
-
let job;
|
|
2500
|
-
let root;
|
|
2501
|
-
for (const r of this.roots.values()) {
|
|
2502
|
-
job = r.jobs.get(id);
|
|
2503
|
-
if (job) {
|
|
2504
|
-
root = r;
|
|
2505
|
-
break;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
if (!job || !root) {
|
|
2509
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
const now = timestamp ?? performance.now();
|
|
2513
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2514
|
-
const delta = deltaMs / 1e3;
|
|
2515
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2516
|
-
const providedState = root.getState?.() ?? {};
|
|
2517
|
-
const frameState = {
|
|
2518
|
-
...providedState,
|
|
2519
|
-
time: now,
|
|
2520
|
-
delta,
|
|
2521
|
-
elapsed,
|
|
2522
|
-
frame: this.loopState.frameCount
|
|
2523
|
-
};
|
|
2524
|
-
try {
|
|
2525
|
-
job.callback(frameState, delta);
|
|
2526
|
-
} catch (error) {
|
|
2527
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2528
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
/**
|
|
2532
|
-
* Execute a single frame across all roots.
|
|
2533
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2534
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2535
|
-
* @returns {void}
|
|
2536
|
-
* @private
|
|
2537
|
-
*/
|
|
2538
|
-
executeFrame(timestamp) {
|
|
2539
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2540
|
-
const delta = deltaMs / 1e3;
|
|
2541
|
-
this.loopState.lastTime = timestamp;
|
|
2542
|
-
this.loopState.frameCount++;
|
|
2543
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2544
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2545
|
-
for (const root of this.roots.values()) {
|
|
2546
|
-
this.tickRoot(root, timestamp, delta);
|
|
2547
|
-
}
|
|
2548
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2549
|
-
}
|
|
2550
|
-
/**
|
|
2551
|
-
* Run all global jobs from a job map.
|
|
2552
|
-
* Catches and logs errors without stopping execution.
|
|
2553
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2554
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2555
|
-
* @returns {void}
|
|
2556
|
-
* @private
|
|
2557
|
-
*/
|
|
2558
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2559
|
-
for (const job of jobs.values()) {
|
|
2560
|
-
try {
|
|
2561
|
-
job.callback(timestamp);
|
|
2562
|
-
} catch (error) {
|
|
2563
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
/**
|
|
2568
|
-
* Execute all jobs for a single root in sorted order.
|
|
2569
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2570
|
-
* Errors are caught and propagated via triggerError.
|
|
2571
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2572
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2573
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2574
|
-
* @returns {void}
|
|
2575
|
-
* @private
|
|
2576
|
-
*/
|
|
2577
|
-
tickRoot(root, timestamp, delta) {
|
|
2578
|
-
if (root.needsRebuild) {
|
|
2579
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2580
|
-
root.needsRebuild = false;
|
|
2581
|
-
}
|
|
2582
|
-
const providedState = root.getState?.() ?? {};
|
|
2583
|
-
const frameState = {
|
|
2584
|
-
...providedState,
|
|
2585
|
-
time: timestamp,
|
|
2586
|
-
delta,
|
|
2587
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2588
|
-
// Convert ms to seconds
|
|
2589
|
-
frame: this.loopState.frameCount
|
|
2590
|
-
};
|
|
2591
|
-
for (const job of root.sortedJobs) {
|
|
2592
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2593
|
-
try {
|
|
2594
|
-
job.callback(frameState, delta);
|
|
2595
|
-
} catch (error) {
|
|
2596
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2597
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
//* Debug & Inspection Methods ================================
|
|
2602
|
-
/**
|
|
2603
|
-
* Get the total number of registered jobs across all roots.
|
|
2604
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2605
|
-
* @returns {number} Total job count
|
|
2606
|
-
*/
|
|
2607
|
-
getJobCount() {
|
|
2608
|
-
let count = 0;
|
|
2609
|
-
for (const root of this.roots.values()) {
|
|
2610
|
-
count += root.jobs.size;
|
|
2611
|
-
}
|
|
2612
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2613
|
-
}
|
|
2614
|
-
/**
|
|
2615
|
-
* Get all registered job IDs across all roots.
|
|
2616
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2617
|
-
* @returns {string[]} Array of all job IDs
|
|
2618
|
-
*/
|
|
2619
|
-
getJobIds() {
|
|
2620
|
-
const ids = [];
|
|
2621
|
-
for (const root of this.roots.values()) {
|
|
2622
|
-
ids.push(...root.jobs.keys());
|
|
2623
|
-
}
|
|
2624
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2625
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2626
|
-
return ids;
|
|
2627
|
-
}
|
|
2628
|
-
/**
|
|
2629
|
-
* Get the number of registered roots (Canvas instances).
|
|
2630
|
-
* @returns {number} Number of registered roots
|
|
2631
|
-
*/
|
|
2632
|
-
getRootCount() {
|
|
2633
|
-
return this.roots.size;
|
|
2634
|
-
}
|
|
2635
|
-
/**
|
|
2636
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2637
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2638
|
-
*
|
|
2639
|
-
* @param phase The phase to check
|
|
2640
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2641
|
-
* @returns true if any user jobs exist in the phase
|
|
2642
|
-
*/
|
|
2643
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2644
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2645
|
-
return rootsToCheck.some((root) => {
|
|
2646
|
-
if (!root) return false;
|
|
2647
|
-
for (const job of root.jobs.values()) {
|
|
2648
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2649
|
-
}
|
|
2650
|
-
return false;
|
|
2651
|
-
});
|
|
2652
|
-
}
|
|
2653
|
-
//* Utility Methods ================================
|
|
2654
|
-
/**
|
|
2655
|
-
* Generate a unique root ID for automatic root registration.
|
|
2656
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2657
|
-
*/
|
|
2658
|
-
generateRootId() {
|
|
2659
|
-
return `root_${this.nextRootIndex++}`;
|
|
2660
|
-
}
|
|
2661
|
-
/**
|
|
2662
|
-
* Generate a unique job ID.
|
|
2663
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2664
|
-
* @private
|
|
2665
|
-
*/
|
|
2666
|
-
generateJobId() {
|
|
2667
|
-
return `job_${this.nextJobIndex}`;
|
|
2668
|
-
}
|
|
2669
|
-
/**
|
|
2670
|
-
* Normalize before/after constraints to a Set.
|
|
2671
|
-
* Handles undefined, single string, or array inputs.
|
|
2672
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2673
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2674
|
-
* @private
|
|
2675
|
-
*/
|
|
2676
|
-
normalizeConstraints(value) {
|
|
2677
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2678
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2679
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2680
|
-
}
|
|
2681
|
-
};
|
|
2682
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2683
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2684
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2685
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2686
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2687
|
-
let Scheduler = _Scheduler;
|
|
2688
|
-
const getScheduler = () => Scheduler.get();
|
|
2689
|
-
if (hmrData) {
|
|
2690
|
-
hmrData.accept?.();
|
|
1675
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1676
|
+
const INTERNAL_FIBER_MARKERS = ["packages/fiber/src", "packages/fiber/dist", "@react-three/fiber", "@react-three_fiber"];
|
|
1677
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1678
|
+
if (!stack) return false;
|
|
1679
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1680
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m)) || INTERNAL_FIBER_MARKERS.some((m) => caller.includes(m));
|
|
2691
1681
|
}
|
|
2692
1682
|
|
|
2693
1683
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
@@ -2799,7 +1789,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2799
1789
|
size: newSize,
|
|
2800
1790
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
2801
1791
|
}));
|
|
2802
|
-
getScheduler().invalidate();
|
|
1792
|
+
scheduler.getScheduler().invalidate();
|
|
2803
1793
|
}
|
|
2804
1794
|
}
|
|
2805
1795
|
return;
|
|
@@ -2814,7 +1804,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2814
1804
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
2815
1805
|
_sizeImperative: true
|
|
2816
1806
|
}));
|
|
2817
|
-
getScheduler().invalidate();
|
|
1807
|
+
scheduler.getScheduler().invalidate();
|
|
2818
1808
|
},
|
|
2819
1809
|
setDpr: (dpr) => set((state2) => {
|
|
2820
1810
|
const resolved = calculateDpr(dpr);
|
|
@@ -2889,8 +1879,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2889
1879
|
const currentState = rootStore.getState();
|
|
2890
1880
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2891
1881
|
const stack = new Error().stack || "";
|
|
2892
|
-
|
|
2893
|
-
if (!isInternalAccess) {
|
|
1882
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2894
1883
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2895
1884
|
notifyDepreciated({
|
|
2896
1885
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -3003,26 +1992,32 @@ function loadingFn(extensions, onProgress) {
|
|
|
3003
1992
|
);
|
|
3004
1993
|
};
|
|
3005
1994
|
}
|
|
3006
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
3007
|
-
const
|
|
1995
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
1996
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
1997
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
3008
1998
|
const fn = loadingFn(extensions, onProgress);
|
|
3009
|
-
const results =
|
|
1999
|
+
const results = cacheKeys.map(
|
|
2000
|
+
(key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2001
|
+
);
|
|
3010
2002
|
return Array.isArray(input) ? results : results[0];
|
|
3011
2003
|
}
|
|
3012
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
3013
|
-
const
|
|
3014
|
-
|
|
2004
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2005
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2006
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2007
|
+
cacheKeys.forEach(
|
|
2008
|
+
(key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2009
|
+
);
|
|
3015
2010
|
};
|
|
3016
|
-
useLoader.clear = function(loader, input) {
|
|
3017
|
-
const
|
|
3018
|
-
|
|
2011
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2012
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2013
|
+
cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
|
|
3019
2014
|
};
|
|
3020
2015
|
useLoader.loader = getLoader;
|
|
3021
2016
|
|
|
3022
2017
|
function useFrame(callback, priorityOrOptions) {
|
|
3023
2018
|
const store = React__namespace.useContext(context);
|
|
3024
2019
|
const isInsideCanvas = store !== null;
|
|
3025
|
-
const scheduler = getScheduler();
|
|
2020
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
3026
2021
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
3027
2022
|
id: priorityOrOptions.id,
|
|
3028
2023
|
phase: priorityOrOptions.phase,
|
|
@@ -3070,7 +2065,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3070
2065
|
};
|
|
3071
2066
|
callbackRef.current?.(mergedState, delta);
|
|
3072
2067
|
};
|
|
3073
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2068
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
3074
2069
|
id,
|
|
3075
2070
|
rootId,
|
|
3076
2071
|
...options
|
|
@@ -3091,37 +2086,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3091
2086
|
}
|
|
3092
2087
|
};
|
|
3093
2088
|
} else {
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
unregisterJob = registerOutside();
|
|
3103
|
-
});
|
|
3104
|
-
return () => {
|
|
3105
|
-
unsubReady();
|
|
3106
|
-
unregisterJob?.();
|
|
3107
|
-
};
|
|
2089
|
+
return scheduler$1.register(
|
|
2090
|
+
(state, delta) => {
|
|
2091
|
+
const frameState = state;
|
|
2092
|
+
if (!frameState.renderer) return;
|
|
2093
|
+
callbackRef.current?.(frameState, delta);
|
|
2094
|
+
},
|
|
2095
|
+
{ id, ...options }
|
|
2096
|
+
);
|
|
3108
2097
|
}
|
|
3109
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2098
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
3110
2099
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
3111
2100
|
// Subscribe function
|
|
3112
2101
|
React__namespace.useCallback(
|
|
3113
2102
|
(onStoreChange) => {
|
|
3114
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2103
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
3115
2104
|
},
|
|
3116
2105
|
[id]
|
|
3117
2106
|
),
|
|
3118
2107
|
// getSnapshot function
|
|
3119
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2108
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
3120
2109
|
// getServerSnapshot function (SSR)
|
|
3121
2110
|
React__namespace.useCallback(() => false, [])
|
|
3122
2111
|
);
|
|
3123
2112
|
const controls = React__namespace.useMemo(() => {
|
|
3124
|
-
const scheduler2 = getScheduler();
|
|
2113
|
+
const scheduler2 = scheduler.getScheduler();
|
|
3125
2114
|
return {
|
|
3126
2115
|
/** The job's unique ID */
|
|
3127
2116
|
id,
|
|
@@ -3136,7 +2125,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3136
2125
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
3137
2126
|
*/
|
|
3138
2127
|
step: (timestamp) => {
|
|
3139
|
-
getScheduler().stepJob(id, timestamp);
|
|
2128
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
3140
2129
|
},
|
|
3141
2130
|
/**
|
|
3142
2131
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -3144,20 +2133,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3144
2133
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
3145
2134
|
*/
|
|
3146
2135
|
stepAll: (timestamp) => {
|
|
3147
|
-
getScheduler().step(timestamp);
|
|
2136
|
+
scheduler.getScheduler().step(timestamp);
|
|
3148
2137
|
},
|
|
3149
2138
|
/**
|
|
3150
2139
|
* Pause this job (set enabled=false).
|
|
3151
2140
|
* Job remains registered but won't run.
|
|
3152
2141
|
*/
|
|
3153
2142
|
pause: () => {
|
|
3154
|
-
getScheduler().pauseJob(id);
|
|
2143
|
+
scheduler.getScheduler().pauseJob(id);
|
|
3155
2144
|
},
|
|
3156
2145
|
/**
|
|
3157
2146
|
* Resume this job (set enabled=true).
|
|
3158
2147
|
*/
|
|
3159
2148
|
resume: () => {
|
|
3160
|
-
getScheduler().resumeJob(id);
|
|
2149
|
+
scheduler.getScheduler().resumeJob(id);
|
|
3161
2150
|
},
|
|
3162
2151
|
/**
|
|
3163
2152
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -3415,7 +2404,7 @@ function addEffect(callback) {
|
|
|
3415
2404
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3416
2405
|
});
|
|
3417
2406
|
const id = `legacy_effect_${effectId++}`;
|
|
3418
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2407
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
3419
2408
|
}
|
|
3420
2409
|
function addAfterEffect(callback) {
|
|
3421
2410
|
notifyDepreciated({
|
|
@@ -3424,7 +2413,7 @@ function addAfterEffect(callback) {
|
|
|
3424
2413
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3425
2414
|
});
|
|
3426
2415
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
3427
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2416
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
3428
2417
|
}
|
|
3429
2418
|
function addTail(callback) {
|
|
3430
2419
|
notifyDepreciated({
|
|
@@ -3432,13 +2421,13 @@ function addTail(callback) {
|
|
|
3432
2421
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
3433
2422
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3434
2423
|
});
|
|
3435
|
-
return getScheduler().onIdle(callback);
|
|
2424
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
3436
2425
|
}
|
|
3437
2426
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
3438
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2427
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
3439
2428
|
}
|
|
3440
2429
|
function advance(timestamp) {
|
|
3441
|
-
getScheduler().step(timestamp);
|
|
2430
|
+
scheduler.getScheduler().step(timestamp);
|
|
3442
2431
|
}
|
|
3443
2432
|
|
|
3444
2433
|
const version = "10.0.0-alpha.2";
|
|
@@ -14880,6 +13869,7 @@ function swapInstances() {
|
|
|
14880
13869
|
}
|
|
14881
13870
|
}
|
|
14882
13871
|
if (instance.isHidden) unhideInstance(instance);
|
|
13872
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14883
13873
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14884
13874
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14885
13875
|
}
|
|
@@ -14892,6 +13882,8 @@ function swapInstances() {
|
|
|
14892
13882
|
instance.object.__r3f = instance;
|
|
14893
13883
|
setFiberRef(fiber, instance.object);
|
|
14894
13884
|
delete instance.appliedOnce;
|
|
13885
|
+
instance.eventCount = 0;
|
|
13886
|
+
instance.handlers = {};
|
|
14895
13887
|
applyProps(instance.object, instance.props);
|
|
14896
13888
|
if (instance.props.attach) {
|
|
14897
13889
|
attach(parent, instance);
|
|
@@ -15196,6 +14188,7 @@ function createRoot(canvas) {
|
|
|
15196
14188
|
const lastConfiguredProps = {};
|
|
15197
14189
|
let configured = false;
|
|
15198
14190
|
let pending = null;
|
|
14191
|
+
let rendererSetup = null;
|
|
15199
14192
|
return {
|
|
15200
14193
|
async configure(props = {}) {
|
|
15201
14194
|
let resolve;
|
|
@@ -15226,7 +14219,7 @@ function createRoot(canvas) {
|
|
|
15226
14219
|
forceEven
|
|
15227
14220
|
} = props;
|
|
15228
14221
|
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
15229
|
-
|
|
14222
|
+
let state = store.getState();
|
|
15230
14223
|
const defaultGPUProps = {
|
|
15231
14224
|
canvas,
|
|
15232
14225
|
antialias: true
|
|
@@ -15240,52 +14233,64 @@ function createRoot(canvas) {
|
|
|
15240
14233
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15241
14234
|
}
|
|
15242
14235
|
let renderer = state.internal.actualRenderer;
|
|
15243
|
-
if (
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
14236
|
+
if (!state.internal.actualRenderer) {
|
|
14237
|
+
if (!rendererSetup) {
|
|
14238
|
+
rendererSetup = (async () => {
|
|
14239
|
+
if (primaryCanvas) {
|
|
14240
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14241
|
+
renderer = primary.renderer;
|
|
14242
|
+
state.internal.actualRenderer = renderer;
|
|
14243
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14244
|
+
primary.store.setState((prev) => ({
|
|
14245
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14246
|
+
}));
|
|
14247
|
+
state.set((prev) => ({
|
|
14248
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14249
|
+
renderer,
|
|
14250
|
+
primaryStore: primary.store,
|
|
14251
|
+
internal: {
|
|
14252
|
+
...prev.internal,
|
|
14253
|
+
canvasTarget,
|
|
14254
|
+
isMultiCanvas: true,
|
|
14255
|
+
isSecondary: true,
|
|
14256
|
+
targetId: primaryCanvas
|
|
14257
|
+
}
|
|
14258
|
+
}));
|
|
14259
|
+
} else {
|
|
14260
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14261
|
+
if (!renderer.hasInitialized?.()) {
|
|
14262
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14263
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14264
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14265
|
+
canvas.width = size2.width * pixelRatio;
|
|
14266
|
+
canvas.height = size2.height * pixelRatio;
|
|
14267
|
+
}
|
|
14268
|
+
await renderer.init();
|
|
14269
|
+
}
|
|
14270
|
+
const backend = renderer.backend;
|
|
14271
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14272
|
+
state.internal.actualRenderer = renderer;
|
|
14273
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14274
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14275
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14276
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14277
|
+
state.set((prev) => ({
|
|
14278
|
+
internal: {
|
|
14279
|
+
...prev.internal,
|
|
14280
|
+
canvasTarget,
|
|
14281
|
+
unregisterPrimary
|
|
14282
|
+
}
|
|
14283
|
+
}));
|
|
14284
|
+
}
|
|
15286
14285
|
}
|
|
15287
|
-
}))
|
|
14286
|
+
})().catch((err) => {
|
|
14287
|
+
rendererSetup = null;
|
|
14288
|
+
throw err;
|
|
14289
|
+
});
|
|
15288
14290
|
}
|
|
14291
|
+
await rendererSetup;
|
|
14292
|
+
state = store.getState();
|
|
14293
|
+
renderer = state.internal.actualRenderer;
|
|
15289
14294
|
}
|
|
15290
14295
|
let raycaster = state.raycaster;
|
|
15291
14296
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -15465,15 +14470,15 @@ function createRoot(canvas) {
|
|
|
15465
14470
|
applyProps(currentRenderer, rendererProps);
|
|
15466
14471
|
}
|
|
15467
14472
|
}
|
|
15468
|
-
const scheduler = getScheduler();
|
|
14473
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
15469
14474
|
const rootId = state.internal.rootId;
|
|
15470
14475
|
if (!rootId) {
|
|
15471
|
-
const newRootId = canvasId || scheduler.generateRootId();
|
|
15472
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14476
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14477
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
15473
14478
|
getState: () => store.getState(),
|
|
15474
14479
|
onError: (err) => store.getState().setError(err)
|
|
15475
14480
|
});
|
|
15476
|
-
const unregisterCanvasTarget = scheduler.register(
|
|
14481
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
15477
14482
|
() => {
|
|
15478
14483
|
const state2 = store.getState();
|
|
15479
14484
|
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
@@ -15488,7 +14493,7 @@ function createRoot(canvas) {
|
|
|
15488
14493
|
system: true
|
|
15489
14494
|
}
|
|
15490
14495
|
);
|
|
15491
|
-
const unregisterEventsFlush = scheduler.register(
|
|
14496
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
15492
14497
|
() => {
|
|
15493
14498
|
const state2 = store.getState();
|
|
15494
14499
|
state2.events.flush?.();
|
|
@@ -15500,7 +14505,7 @@ function createRoot(canvas) {
|
|
|
15500
14505
|
system: true
|
|
15501
14506
|
}
|
|
15502
14507
|
);
|
|
15503
|
-
const unregisterFrustum = scheduler.register(
|
|
14508
|
+
const unregisterFrustum = scheduler$1.register(
|
|
15504
14509
|
() => {
|
|
15505
14510
|
const state2 = store.getState();
|
|
15506
14511
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -15510,11 +14515,11 @@ function createRoot(canvas) {
|
|
|
15510
14515
|
{
|
|
15511
14516
|
id: `${newRootId}_frustum`,
|
|
15512
14517
|
rootId: newRootId,
|
|
15513
|
-
|
|
14518
|
+
before: "render",
|
|
15514
14519
|
system: true
|
|
15515
14520
|
}
|
|
15516
14521
|
);
|
|
15517
|
-
const unregisterVisibility = scheduler.register(
|
|
14522
|
+
const unregisterVisibility = scheduler$1.register(
|
|
15518
14523
|
() => {
|
|
15519
14524
|
const state2 = store.getState();
|
|
15520
14525
|
checkVisibility(state2);
|
|
@@ -15522,16 +14527,16 @@ function createRoot(canvas) {
|
|
|
15522
14527
|
{
|
|
15523
14528
|
id: `${newRootId}_visibility`,
|
|
15524
14529
|
rootId: newRootId,
|
|
15525
|
-
|
|
14530
|
+
before: "render",
|
|
15526
14531
|
system: true,
|
|
15527
14532
|
after: `${newRootId}_frustum`
|
|
15528
14533
|
}
|
|
15529
14534
|
);
|
|
15530
|
-
const unregisterRender = scheduler.register(
|
|
14535
|
+
const unregisterRender = scheduler$1.register(
|
|
15531
14536
|
() => {
|
|
15532
14537
|
const state2 = store.getState();
|
|
15533
14538
|
const renderer2 = state2.internal.actualRenderer;
|
|
15534
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14539
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
15535
14540
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15536
14541
|
try {
|
|
15537
14542
|
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
@@ -15564,11 +14569,11 @@ function createRoot(canvas) {
|
|
|
15564
14569
|
unregisterVisibility();
|
|
15565
14570
|
unregisterRender();
|
|
15566
14571
|
},
|
|
15567
|
-
scheduler
|
|
14572
|
+
scheduler: scheduler$1
|
|
15568
14573
|
}
|
|
15569
14574
|
}));
|
|
15570
14575
|
}
|
|
15571
|
-
scheduler.frameloop = frameloop;
|
|
14576
|
+
scheduler$1.frameloop = frameloop;
|
|
15572
14577
|
onCreated = onCreatedCallback;
|
|
15573
14578
|
configured = true;
|
|
15574
14579
|
resolve();
|
|
@@ -15698,6 +14703,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15698
14703
|
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
15699
14704
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15700
14705
|
}
|
|
14706
|
+
const set = injectState.set;
|
|
15701
14707
|
return {
|
|
15702
14708
|
// The intersect consists of the previous root state
|
|
15703
14709
|
...rootState,
|
|
@@ -15716,7 +14722,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15716
14722
|
size: resolvedSize,
|
|
15717
14723
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15718
14724
|
// Layers are allowed to override events
|
|
15719
|
-
setEvents: (events2) =>
|
|
14725
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15720
14726
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15721
14727
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15722
14728
|
};
|
|
@@ -15745,6 +14751,59 @@ function flushSync(fn) {
|
|
|
15745
14751
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15746
14752
|
}
|
|
15747
14753
|
|
|
14754
|
+
function parseBackground(background) {
|
|
14755
|
+
if (!background) return null;
|
|
14756
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14757
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14758
|
+
return {
|
|
14759
|
+
...rest,
|
|
14760
|
+
preset,
|
|
14761
|
+
files: envMap || files,
|
|
14762
|
+
backgroundFiles: backgroundMap,
|
|
14763
|
+
background: true
|
|
14764
|
+
};
|
|
14765
|
+
}
|
|
14766
|
+
if (typeof background === "number") {
|
|
14767
|
+
return { color: background, background: true };
|
|
14768
|
+
}
|
|
14769
|
+
if (typeof background === "string") {
|
|
14770
|
+
if (background in presetsObj) {
|
|
14771
|
+
return { preset: background, background: true };
|
|
14772
|
+
}
|
|
14773
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14774
|
+
return { files: background, background: true };
|
|
14775
|
+
}
|
|
14776
|
+
return { color: background, background: true };
|
|
14777
|
+
}
|
|
14778
|
+
if (background.isColor) {
|
|
14779
|
+
return { color: background, background: true };
|
|
14780
|
+
}
|
|
14781
|
+
return null;
|
|
14782
|
+
}
|
|
14783
|
+
|
|
14784
|
+
function parseRendererConfig(rendererProp) {
|
|
14785
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14786
|
+
if (isRendererConfig) {
|
|
14787
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14788
|
+
return {
|
|
14789
|
+
primaryCanvas: pc,
|
|
14790
|
+
scheduler: sc,
|
|
14791
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14792
|
+
};
|
|
14793
|
+
}
|
|
14794
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14795
|
+
}
|
|
14796
|
+
|
|
14797
|
+
function clearHmrCaches(store) {
|
|
14798
|
+
store.setState((state) => ({
|
|
14799
|
+
nodes: {},
|
|
14800
|
+
uniforms: {},
|
|
14801
|
+
buffers: {},
|
|
14802
|
+
gpuStorage: {},
|
|
14803
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14804
|
+
}));
|
|
14805
|
+
}
|
|
14806
|
+
|
|
15748
14807
|
function CanvasImpl({
|
|
15749
14808
|
ref,
|
|
15750
14809
|
children,
|
|
@@ -15765,6 +14824,8 @@ function CanvasImpl({
|
|
|
15765
14824
|
raycaster,
|
|
15766
14825
|
camera,
|
|
15767
14826
|
scene,
|
|
14827
|
+
autoUpdateFrustum,
|
|
14828
|
+
occlusion,
|
|
15768
14829
|
onPointerMissed,
|
|
15769
14830
|
onDragOverMissed,
|
|
15770
14831
|
onDropMissed,
|
|
@@ -15776,49 +14837,10 @@ function CanvasImpl({
|
|
|
15776
14837
|
forceEven,
|
|
15777
14838
|
...props
|
|
15778
14839
|
}) {
|
|
15779
|
-
const
|
|
15780
|
-
let primaryCanvas;
|
|
15781
|
-
let scheduler;
|
|
15782
|
-
let renderer;
|
|
15783
|
-
if (isRendererConfig) {
|
|
15784
|
-
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
15785
|
-
primaryCanvas = pc;
|
|
15786
|
-
scheduler = sc;
|
|
15787
|
-
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
15788
|
-
} else {
|
|
15789
|
-
renderer = rendererProp;
|
|
15790
|
-
}
|
|
14840
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15791
14841
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15792
14842
|
const Bridge = useBridge();
|
|
15793
|
-
const backgroundProps = React__namespace.useMemo(() =>
|
|
15794
|
-
if (!background) return null;
|
|
15795
|
-
if (typeof background === "object" && !background.isColor) {
|
|
15796
|
-
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
15797
|
-
return {
|
|
15798
|
-
...rest,
|
|
15799
|
-
preset,
|
|
15800
|
-
files: envMap || files,
|
|
15801
|
-
backgroundFiles: backgroundMap,
|
|
15802
|
-
background: true
|
|
15803
|
-
};
|
|
15804
|
-
}
|
|
15805
|
-
if (typeof background === "number") {
|
|
15806
|
-
return { color: background, background: true };
|
|
15807
|
-
}
|
|
15808
|
-
if (typeof background === "string") {
|
|
15809
|
-
if (background in presetsObj) {
|
|
15810
|
-
return { preset: background, background: true };
|
|
15811
|
-
}
|
|
15812
|
-
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
15813
|
-
return { files: background, background: true };
|
|
15814
|
-
}
|
|
15815
|
-
return { color: background, background: true };
|
|
15816
|
-
}
|
|
15817
|
-
if (background.isColor) {
|
|
15818
|
-
return { color: background, background: true };
|
|
15819
|
-
}
|
|
15820
|
-
return null;
|
|
15821
|
-
}, [background]);
|
|
14843
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15822
14844
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15823
14845
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15824
14846
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15860,6 +14882,7 @@ function CanvasImpl({
|
|
|
15860
14882
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15861
14883
|
const [block, setBlock] = React__namespace.useState(false);
|
|
15862
14884
|
const [error, setError] = React__namespace.useState(false);
|
|
14885
|
+
const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
|
|
15863
14886
|
if (block) throw block;
|
|
15864
14887
|
if (error) throw error;
|
|
15865
14888
|
const root = React__namespace.useRef(null);
|
|
@@ -15867,6 +14890,7 @@ function CanvasImpl({
|
|
|
15867
14890
|
const unsubscribeErrorRef = React__namespace.useRef(null);
|
|
15868
14891
|
useIsomorphicLayoutEffect(() => {
|
|
15869
14892
|
effectActiveRef.current = true;
|
|
14893
|
+
if (fallbackVisible) return;
|
|
15870
14894
|
const canvas = canvasRef.current;
|
|
15871
14895
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15872
14896
|
if (!root.current) {
|
|
@@ -15887,7 +14911,7 @@ function CanvasImpl({
|
|
|
15887
14911
|
}
|
|
15888
14912
|
async function run() {
|
|
15889
14913
|
if (!effectActiveRef.current || !root.current) return;
|
|
15890
|
-
await root.current.configure({
|
|
14914
|
+
const configured = await root.current.configure({
|
|
15891
14915
|
id,
|
|
15892
14916
|
primaryCanvas,
|
|
15893
14917
|
scheduler,
|
|
@@ -15902,6 +14926,8 @@ function CanvasImpl({
|
|
|
15902
14926
|
performance,
|
|
15903
14927
|
raycaster,
|
|
15904
14928
|
camera,
|
|
14929
|
+
autoUpdateFrustum,
|
|
14930
|
+
occlusion,
|
|
15905
14931
|
size: effectiveSize,
|
|
15906
14932
|
// Store size props for reset functionality
|
|
15907
14933
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15926,8 +14952,20 @@ function CanvasImpl({
|
|
|
15926
14952
|
}
|
|
15927
14953
|
onCreated?.(state);
|
|
15928
14954
|
}
|
|
15929
|
-
})
|
|
15930
|
-
|
|
14955
|
+
}).then(
|
|
14956
|
+
() => true,
|
|
14957
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
14958
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
14959
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
14960
|
+
(setupError) => {
|
|
14961
|
+
if (effectActiveRef.current) {
|
|
14962
|
+
if (fallback != null) setFallbackVisible(true);
|
|
14963
|
+
else setError(setupError);
|
|
14964
|
+
}
|
|
14965
|
+
return false;
|
|
14966
|
+
}
|
|
14967
|
+
);
|
|
14968
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15931
14969
|
root.current.render(
|
|
15932
14970
|
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
|
|
15933
14971
|
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
@@ -15961,14 +14999,7 @@ function CanvasImpl({
|
|
|
15961
14999
|
const handleHMR = () => {
|
|
15962
15000
|
queueMicrotask(() => {
|
|
15963
15001
|
const rootEntry = _roots.get(canvas);
|
|
15964
|
-
if (rootEntry?.store)
|
|
15965
|
-
console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
|
|
15966
|
-
rootEntry.store.setState((state) => ({
|
|
15967
|
-
nodes: {},
|
|
15968
|
-
uniforms: {},
|
|
15969
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15970
|
-
}));
|
|
15971
|
-
}
|
|
15002
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15972
15003
|
});
|
|
15973
15004
|
};
|
|
15974
15005
|
if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
|
|
@@ -15997,7 +15028,11 @@ function CanvasImpl({
|
|
|
15997
15028
|
...style
|
|
15998
15029
|
},
|
|
15999
15030
|
...props,
|
|
16000
|
-
children:
|
|
15031
|
+
children: fallbackVisible ? (
|
|
15032
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15033
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15034
|
+
fallback
|
|
15035
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16001
15036
|
"canvas",
|
|
16002
15037
|
{
|
|
16003
15038
|
ref: canvasRef,
|
|
@@ -16108,6 +15143,20 @@ function createLazyCreatorState(state) {
|
|
|
16108
15143
|
});
|
|
16109
15144
|
}
|
|
16110
15145
|
|
|
15146
|
+
function resolvePrimary(local) {
|
|
15147
|
+
return local.getState().primaryStore ?? local;
|
|
15148
|
+
}
|
|
15149
|
+
function usePrimaryStore() {
|
|
15150
|
+
const local = useStore();
|
|
15151
|
+
const primary = local.getState().primaryStore;
|
|
15152
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15153
|
+
}
|
|
15154
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15155
|
+
const local = useStore();
|
|
15156
|
+
const primary = resolvePrimary(local);
|
|
15157
|
+
return primary(selector, equalityFn);
|
|
15158
|
+
}
|
|
15159
|
+
|
|
16111
15160
|
function addTexture(set, key, value) {
|
|
16112
15161
|
set((state) => {
|
|
16113
15162
|
const newMap = new Map(state.textures);
|
|
@@ -16213,7 +15262,7 @@ function useCompareMemoize(value, deep) {
|
|
|
16213
15262
|
|
|
16214
15263
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16215
15264
|
function useUniforms(creatorOrScope, scope) {
|
|
16216
|
-
const store =
|
|
15265
|
+
const store = usePrimaryStore();
|
|
16217
15266
|
const removeUniforms2 = React.useCallback(
|
|
16218
15267
|
(names, targetScope) => {
|
|
16219
15268
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -16283,8 +15332,8 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
16283
15332
|
}, [creatorOrScope, store]);
|
|
16284
15333
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
16285
15334
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
16286
|
-
const storeUniforms =
|
|
16287
|
-
const hmrVersion =
|
|
15335
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15336
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16288
15337
|
const readerDep = isReader ? storeUniforms : null;
|
|
16289
15338
|
const creatorDep = isReader ? null : hmrVersion;
|
|
16290
15339
|
const uniforms = React.useMemo(() => {
|
|
@@ -16345,6 +15394,7 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
16345
15394
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16346
15395
|
}
|
|
16347
15396
|
function rebuildAllUniforms(store, scope) {
|
|
15397
|
+
store = store.getState().primaryStore ?? store;
|
|
16348
15398
|
store.setState((state) => {
|
|
16349
15399
|
let newUniforms = {};
|
|
16350
15400
|
if (scope && scope !== "root") {
|
|
@@ -16473,7 +15523,7 @@ function useUniform(name, value) {
|
|
|
16473
15523
|
|
|
16474
15524
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16475
15525
|
function useNodes(creatorOrScope, scope) {
|
|
16476
|
-
const store =
|
|
15526
|
+
const store = usePrimaryStore();
|
|
16477
15527
|
const removeNodes2 = React.useCallback(
|
|
16478
15528
|
(names, targetScope) => {
|
|
16479
15529
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -16530,8 +15580,8 @@ function useNodes(creatorOrScope, scope) {
|
|
|
16530
15580
|
[store]
|
|
16531
15581
|
);
|
|
16532
15582
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16533
|
-
const storeNodes =
|
|
16534
|
-
const hmrVersion =
|
|
15583
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15584
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16535
15585
|
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
16536
15586
|
const readerDep = isReader ? storeNodes : null;
|
|
16537
15587
|
const creatorDep = isReader ? null : hmrVersion;
|
|
@@ -16590,6 +15640,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
16590
15640
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16591
15641
|
}
|
|
16592
15642
|
function rebuildAllNodes(store, scope) {
|
|
15643
|
+
store = store.getState().primaryStore ?? store;
|
|
16593
15644
|
store.setState((state) => {
|
|
16594
15645
|
let newNodes = state.nodes;
|
|
16595
15646
|
if (scope && scope !== "root") {
|
|
@@ -16634,11 +15685,11 @@ function clearRootNodes(set) {
|
|
|
16634
15685
|
});
|
|
16635
15686
|
}
|
|
16636
15687
|
function useLocalNodes(creator) {
|
|
16637
|
-
const store =
|
|
16638
|
-
const uniforms =
|
|
16639
|
-
const nodes =
|
|
16640
|
-
const textures =
|
|
16641
|
-
const hmrVersion =
|
|
15688
|
+
const store = usePrimaryStore();
|
|
15689
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15690
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15691
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15692
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16642
15693
|
return React.useMemo(() => {
|
|
16643
15694
|
const wrappedState = createLazyCreatorState(store.getState());
|
|
16644
15695
|
return creator(wrappedState);
|
|
@@ -16659,7 +15710,7 @@ const disposeBuffer = (buffer) => {
|
|
|
16659
15710
|
}
|
|
16660
15711
|
};
|
|
16661
15712
|
function useBuffers(creatorOrScope, scope) {
|
|
16662
|
-
const store =
|
|
15713
|
+
const store = usePrimaryStore();
|
|
16663
15714
|
const removeBuffers = React.useCallback(
|
|
16664
15715
|
(names, targetScope) => {
|
|
16665
15716
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -16730,8 +15781,8 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
16730
15781
|
[store, removeBuffers]
|
|
16731
15782
|
);
|
|
16732
15783
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16733
|
-
const storeBuffers =
|
|
16734
|
-
const hmrVersion =
|
|
15784
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15785
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16735
15786
|
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
16736
15787
|
const readerDep = isReader ? storeBuffers : null;
|
|
16737
15788
|
const creatorDep = isReader ? null : hmrVersion;
|
|
@@ -16794,6 +15845,7 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
16794
15845
|
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
16795
15846
|
}
|
|
16796
15847
|
function rebuildAllBuffers(store, scope) {
|
|
15848
|
+
store = store.getState().primaryStore ?? store;
|
|
16797
15849
|
store.setState((state) => {
|
|
16798
15850
|
let newBuffers = state.buffers;
|
|
16799
15851
|
if (scope && scope !== "root") {
|
|
@@ -16825,7 +15877,7 @@ const disposeStorage = (storage) => {
|
|
|
16825
15877
|
}
|
|
16826
15878
|
};
|
|
16827
15879
|
function useGPUStorage(creatorOrScope, scope) {
|
|
16828
|
-
const store =
|
|
15880
|
+
const store = usePrimaryStore();
|
|
16829
15881
|
const removeStorage = React.useCallback(
|
|
16830
15882
|
(names, targetScope) => {
|
|
16831
15883
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -16896,8 +15948,8 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
16896
15948
|
[store, removeStorage]
|
|
16897
15949
|
);
|
|
16898
15950
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16899
|
-
const storeStorage =
|
|
16900
|
-
const hmrVersion =
|
|
15951
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15952
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16901
15953
|
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
16902
15954
|
const readerDep = isReader ? storeStorage : null;
|
|
16903
15955
|
const creatorDep = isReader ? null : hmrVersion;
|
|
@@ -16972,6 +16024,7 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
16972
16024
|
};
|
|
16973
16025
|
}
|
|
16974
16026
|
function rebuildAllStorage(store, scope) {
|
|
16027
|
+
store = store.getState().primaryStore ?? store;
|
|
16975
16028
|
store.setState((state) => {
|
|
16976
16029
|
let newStorage = state.gpuStorage;
|
|
16977
16030
|
if (scope && scope !== "root") {
|
|
@@ -17084,6 +16137,8 @@ function useRenderPipeline(mainCB, setupCB) {
|
|
|
17084
16137
|
|
|
17085
16138
|
extend(THREE);
|
|
17086
16139
|
|
|
16140
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
16141
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
17087
16142
|
exports.Block = Block;
|
|
17088
16143
|
exports.Canvas = Canvas;
|
|
17089
16144
|
exports.Environment = Environment;
|
|
@@ -17099,7 +16154,6 @@ exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
|
17099
16154
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
17100
16155
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
17101
16156
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
17102
|
-
exports.Scheduler = Scheduler;
|
|
17103
16157
|
exports.Texture = Texture;
|
|
17104
16158
|
exports._roots = _roots;
|
|
17105
16159
|
exports.act = act;
|
|
@@ -17135,7 +16189,6 @@ exports.getInstanceProps = getInstanceProps;
|
|
|
17135
16189
|
exports.getPrimary = getPrimary;
|
|
17136
16190
|
exports.getPrimaryIds = getPrimaryIds;
|
|
17137
16191
|
exports.getRootState = getRootState;
|
|
17138
|
-
exports.getScheduler = getScheduler;
|
|
17139
16192
|
exports.getUuidPrefix = getUuidPrefix;
|
|
17140
16193
|
exports.hasConstructor = hasConstructor;
|
|
17141
16194
|
exports.hasPrimary = hasPrimary;
|