Skip to content

Instantly share code, notes, and snippets.

@intrnl
Created June 8, 2022 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save intrnl/9fac9ce36f77bc72f042e03cffb06dac to your computer and use it in GitHub Desktop.
Save intrnl/9fac9ce36f77bc72f042e03cffb06dac to your computer and use it in GitHub Desktop.
/** @type {?Scope} */
let curr_scope = null;
/**
* @param {boolean} [detached]
* @returns {Scope}
*/
export function scope (detached) {
let instance = new Scope(detached);
return instance;
}
/**
* @param {number} dependencies
* @param {() => void} runner
*/
export function effect (dependencies, runner) {
runner();
if (dependencies && curr_scope) {
curr_scope._track(dependencies, runner);
}
}
class Scope {
/** @type {?boolean} */
_disabled = false;
/** @type {number} */
_dependencies = 0;
/** @type {[dependencies: number, effect: (() => void)][]} */
_effects = [];
/** @type {(() => void)[]} */
_cleanups = [];
/** @type {Scope[]} */
_scopes = [];
/** @type {?Scope} */
_parent;
/** @type {?number} */
_parent_idx;
/**
* @param {boolean} [detached]
*/
constructor (detached) {
let instance = this;
if (!detached && curr_scope) {
instance._parent = curr_scope;
instance._parent_idx = curr_scope._scopes.push(instance) - 1;
}
}
/**
* @param {number} dirty
*/
_invalidate (dirty) {
let instance = this;
let scopes = instance._scopes;
if (instance._dependencies & dirty) {
for (let [dependencies, runner] of instance._effects) {
if (dependencies & dirty) {
runner();
}
}
}
if (scopes.length) {
for (let scope of scopes) {
scope._invalidate(dirty);
}
}
}
/**
* @param {number} dependencies
* @param {() => void} runner
*/
_track (dependencies, runner) {
let instance = this;
instance._effects.push([dependencies, runner]);
instance._dependencies |= dependencies;
}
/**
* @template T
* @param {() => T} fn
* @returns {T}
*/
_run (fn) {
let instance = this;
if (instance._disabled) {
return;
}
let prev_scope = curr_scope;
try {
curr_scope = instance;
return fn();
}
finally {
curr_scope = prev_scope;
}
}
/**
* @param {boolean} [from_parent]
*/
_stop (from_parent) {
let instance = this;
let parent = !from_parent && instance._parent;
instance._disabled = true;
instance._clear();
if (parent) {
let last = parent._scopes.pop();
let idx = instance._parent_idx;
if (last && last !== instance) {
parent._scopes[idx] = last;
last._parent_idx = idx;
}
}
}
_clear () {
let instance = this;
let effects = instance._effects;
let cleanups = instance._cleanups;
let scopes = instance._scopes;
for (let cleanup of cleanups) {
cleanup();
}
for (let scope of scopes) {
scope._stop(true);
}
effects.length = 0;
cleanups.length = 0;
// scopes.length = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment