perf: bail early when traversing non-state (#10654)

This has a lot of overhead for large lists, and we can at least diminish in the "no state proxy" case by applying a sensible heuristic:
- If the value passed is a state proxy, read it
- If not, and if the value is an array, then bail because an array of state proxies is highly unlikely
- Traverse the first level of properties of the object and look if these are state, if not bail. State proxies nested further down are highly unlikely, too

part of #10637
pull/10656/head
Simon H 9 months ago committed by GitHub
parent 6625c1e080
commit 3fe4940a9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
perf: bail early when traversing non-state

@ -170,7 +170,7 @@ export const javascript_visitors_legacy = {
// If the binding is a prop, we need to deep read it because it could be fine-grained $state // If the binding is a prop, we need to deep read it because it could be fine-grained $state
// from a runes-component, where mutations don't trigger an update on the prop as a whole. // from a runes-component, where mutations don't trigger an update on the prop as a whole.
if (name === '$$props' || name === '$$restProps' || binding.kind === 'prop') { if (name === '$$props' || name === '$$restProps' || binding.kind === 'prop') {
serialized = b.call('$.deep_read', serialized); serialized = b.call('$.deep_read_state', serialized);
} }
sequence.push(serialized); sequence.push(serialized);

@ -44,11 +44,11 @@ import {
push, push,
pop, pop,
current_component_context, current_component_context,
deep_read,
get, get,
set, set,
is_signals_recorded, is_signals_recorded,
inspect_fn inspect_fn,
deep_read_state
} from './runtime.js'; } from './runtime.js';
import { import {
render_effect, render_effect,
@ -1950,7 +1950,7 @@ export function action(dom, action, value_fn) {
// This works in legacy mode because of mutable_source being updated as a whole, but when using $state // This works in legacy mode because of mutable_source being updated as a whole, but when using $state
// together with actions and mutation, it wouldn't notice the change without a deep read. // together with actions and mutation, it wouldn't notice the change without a deep read.
if (needs_deep_read) { if (needs_deep_read) {
deep_read(value); deep_read_state(value);
} }
} else { } else {
untrack(() => (payload = action(dom))); untrack(() => (payload = action(dom)));
@ -2858,10 +2858,12 @@ export function init() {
if (!callbacks) return; if (!callbacks) return;
// beforeUpdate // beforeUpdate
pre_effect(() => { if (callbacks.b.length) {
observe_all(context); pre_effect(() => {
callbacks.b.forEach(run); observe_all(context);
}); callbacks.b.forEach(run);
});
}
// onMount (must run before afterUpdate) // onMount (must run before afterUpdate)
user_effect(() => { user_effect(() => {
@ -2876,10 +2878,12 @@ export function init() {
}); });
// afterUpdate // afterUpdate
user_effect(() => { if (callbacks.a.length) {
observe_all(context); user_effect(() => {
callbacks.a.forEach(run); observe_all(context);
}); callbacks.a.forEach(run);
});
}
} }
/** /**
@ -2892,7 +2896,7 @@ function observe_all(context) {
for (const signal of context.d) get(signal); for (const signal of context.d) get(signal);
} }
deep_read(context.s); deep_read_state(context.s);
} }
/** /**

@ -1244,6 +1244,29 @@ export function pop(component) {
return component || /** @type {T} */ ({}); return component || /** @type {T} */ ({});
} }
/**
* Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`.
* Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases).
* @param {any} value
* @returns {void}
*/
export function deep_read_state(value) {
if (typeof value !== 'object' || !value || value instanceof EventTarget) {
return;
}
if (STATE_SYMBOL in value) {
deep_read(value);
} else if (!Array.isArray(value)) {
for (let key in value) {
const prop = value[key];
if (typeof prop === 'object' && prop && STATE_SYMBOL in prop) {
deep_read(prop);
}
}
}
}
/** /**
* Deeply traverse an object and read all its properties * Deeply traverse an object and read all its properties
* so that they're all reactive in case this is `$state` * so that they're all reactive in case this is `$state`

@ -18,7 +18,8 @@ export {
inspect, inspect,
unwrap, unwrap,
freeze, freeze,
deep_read deep_read,
deep_read_state
} from './client/runtime.js'; } from './client/runtime.js';
export * from './client/dev/ownership.js'; export * from './client/dev/ownership.js';
export { await_block as await } from './client/dom/blocks/await.js'; export { await_block as await } from './client/dom/blocks/await.js';

Loading…
Cancel
Save