alternative approach

freeze-destroyed-consts
Rich Harris 2 months ago
parent 0d7b8a6abb
commit 3eed592ac1

@ -19,7 +19,10 @@ export function ConstTag(node, context) {
if (declaration.id.type === 'Identifier') {
const init = build_expression(context, declaration.init, node.metadata.expression);
let expression = create_derived(context.state, init, node.metadata.expression.has_await);
let expression = b.call(
'$.const_tag',
create_derived(context.state, init, node.metadata.expression.has_await)
);
if (dev) {
expression = b.call('$.tag', expression, b.literal(declaration.id.name));

@ -49,6 +49,7 @@ export const EFFECT_OFFSCREEN = 1 << 25;
* because a derived might be checked but not executed).
*/
export const WAS_MARKED = 1 << 16;
export const CONST_TAG = 1 << 26;
// Flags used for async
export const REACTION_IS_UPDATING = 1 << 21;

@ -109,6 +109,7 @@ export {
export { eager, flushSync as flush } from './reactivity/batch.js';
export {
async_derived,
const_tag,
user_derived as derived,
derived_safe_equal
} from './reactivity/deriveds.js';

@ -12,7 +12,8 @@ import {
DESTROYED,
CLEAN,
INERT,
BRANCH_EFFECT
BRANCH_EFFECT,
CONST_TAG
} from '#client/constants';
import {
active_reaction,
@ -64,10 +65,6 @@ export const recent_async_deriveds = new Set();
/*#__NO_SIDE_EFFECTS__*/
export function derived(fn) {
var flags = DERIVED | DIRTY;
var parent_derived =
active_reaction !== null && (active_reaction.f & DERIVED) !== 0
? /** @type {Derived} */ (active_reaction)
: null;
if (active_effect !== null) {
// Since deriveds are evaluated lazily, any effects created inside them are
@ -87,7 +84,7 @@ export function derived(fn) {
rv: 0,
v: /** @type {V} */ (UNINITIALIZED),
wv: 0,
parent: parent_derived ?? active_effect,
parent: active_effect,
ac: null
};
@ -98,6 +95,17 @@ export function derived(fn) {
return signal;
}
/**
* @template V
* @param {Derived<V>} d
* @returns {Derived<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function const_tag(d) {
d.f |= CONST_TAG;
return d;
}
/**
* @template V
* @param {() => V | Promise<V>} fn
@ -284,47 +292,19 @@ export function destroy_derived_effects(derived) {
*/
let stack = [];
/**
* @param {Derived} derived
* @returns {Effect | null}
*/
function get_derived_parent_effect(derived) {
var parent = derived.parent;
while (parent !== null) {
if ((parent.f & DERIVED) === 0) {
return /** @type {Effect} */ (parent);
}
parent = parent.parent;
}
return null;
}
/**
* @template T
* @param {Derived} derived
* @returns {T}
*/
export function execute_derived(derived) {
var raw_parent = get_derived_parent_effect(derived);
var parent_effect = raw_parent !== null && (raw_parent.f & DESTROYED) !== 0 ? null : raw_parent;
var parent_effect = derived.parent;
// don't update `{@const ...}` in an outroing block
// don't update `{@const ...}` in an outroing or destroyed block
if (
!async_mode_flag &&
!is_destroying_effect &&
(derived.f & CONST_TAG) !== 0 &&
parent_effect !== null &&
(parent_effect.f & INERT) !== 0
) {
return derived.v;
}
// don't update deriveds inside a destroyed branch (e.g. {#if} or {#each}) —
// the branch scope is invalid and evaluating could trigger side effects
// with stale values.
if (
!is_destroying_effect &&
raw_parent !== null &&
(raw_parent.f & (DESTROYED | BRANCH_EFFECT)) === (DESTROYED | BRANCH_EFFECT)
(parent_effect.f & (INERT | DESTROYED)) !== 0
) {
return derived.v;
}

@ -57,8 +57,8 @@ export interface Derived<V = unknown> extends Value<V>, Reaction {
fn: () => V;
/** Effects created inside this signal. Used to destroy those effects when the derived reruns or is cleaned up */
effects: null | Effect[];
/** Parent effect or derived */
parent: Effect | Derived | null;
/** Parent effect */
parent: Effect | null;
}
export interface EffectNodes {

@ -527,12 +527,7 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
// if we're in a derived that is being read inside an _async_ derived,
// it's possible that the effect was already destroyed. In this case,
// we don't add the dependency, because that would create a memory leak
var destroyed = active_effect !== null && (active_effect.f & DESTROYED) !== 0;
if (!destroyed && (current_sources === null || !includes.call(current_sources, signal))) {
if (current_sources === null || !includes.call(current_sources, signal)) {
var deps = active_reaction.deps;
if ((active_reaction.f & REACTION_IS_UPDATING) !== 0) {

@ -14,8 +14,8 @@ export default function Async_const($$anchor) {
let b;
var promises = $.run([
async () => a = (await $.save($.async_derived(async () => (await $.save(1))())))(),
() => b = $.derived(() => $.get(a) + 1)
async () => a = $.const_tag((await $.save($.async_derived(async () => (await $.save(1))())))()),
() => b = $.const_tag($.derived(() => $.get(a) + 1))
]);
var p = root_1();

@ -30,15 +30,15 @@ export default function Async_in_derived($$anchor, $$props) {
let no2;
var promises = $.run([
async () => yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))(),
async () => yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))(),
() => no1 = $.derived(() => (async () => {
async () => yes1 = $.const_tag((await $.save($.async_derived(async () => (await $.save(1))())))()),
async () => yes2 = $.const_tag((await $.save($.async_derived(async () => foo((await $.save(1))()))))()),
() => no1 = $.const_tag($.derived(() => (async () => {
return await 1;
})()),
})())),
() => no2 = $.derived(() => (async () => {
() => no2 = $.const_tag($.derived(() => (async () => {
return await 1;
})())
})()))
]);
};

@ -138,7 +138,7 @@ export default function Select_with_rich_content($$anchor) {
var select_5 = $.sibling(select_4, 2);
$.each(select_5, 5, () => items, $.index, ($$anchor, item) => {
const x = $.derived_safe_equal(() => $.get(item) * 2);
const x = $.const_tag($.derived_safe_equal(() => $.get(item) * 2));
var option_8 = root_8();
var text_1 = $.child(option_8, true);

Loading…
Cancel
Save