packages/recoil/core/Recoil_Graph.js (130 lines of code) (raw):
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
* @flow strict
* @format
*/
'use strict';
import type {DependencyMap, Graph} from './Recoil_GraphTypes';
import type {NodeKey, StateID} from './Recoil_Keys';
import type {Store} from './Recoil_State';
const differenceSets = require('recoil-shared/util/Recoil_differenceSets');
const mapMap = require('recoil-shared/util/Recoil_mapMap');
const nullthrows = require('recoil-shared/util/Recoil_nullthrows');
const recoverableViolation = require('recoil-shared/util/Recoil_recoverableViolation');
export type {DependencyMap, Graph} from './Recoil_GraphTypes';
function makeGraph(): Graph {
return {
nodeDeps: new Map(),
nodeToNodeSubscriptions: new Map(),
};
}
function cloneGraph(graph: Graph): Graph {
return {
nodeDeps: mapMap(graph.nodeDeps, s => new Set(s)),
nodeToNodeSubscriptions: mapMap(
graph.nodeToNodeSubscriptions,
s => new Set(s),
),
};
}
// Note that this overwrites the deps of existing nodes, rather than unioning
// the new deps with the old deps.
function mergeDependencyMapIntoGraph(
deps: DependencyMap,
graph: Graph,
// If olderGraph is given then we will not overwrite changes made to the given
// graph compared with olderGraph:
olderGraph?: Graph,
): void {
const {nodeDeps, nodeToNodeSubscriptions} = graph;
deps.forEach((upstreams, downstream) => {
const existingUpstreams = nodeDeps.get(downstream);
if (
existingUpstreams &&
olderGraph &&
existingUpstreams !== olderGraph.nodeDeps.get(downstream)
) {
return;
}
// Update nodeDeps:
nodeDeps.set(downstream, new Set(upstreams));
// Add new deps to nodeToNodeSubscriptions:
const addedUpstreams =
existingUpstreams == null
? upstreams
: differenceSets(upstreams, existingUpstreams);
addedUpstreams.forEach(upstream => {
if (!nodeToNodeSubscriptions.has(upstream)) {
nodeToNodeSubscriptions.set(upstream, new Set());
}
const existing = nullthrows(nodeToNodeSubscriptions.get(upstream));
existing.add(downstream);
});
// Remove removed deps from nodeToNodeSubscriptions:
if (existingUpstreams) {
const removedUpstreams = differenceSets(existingUpstreams, upstreams);
removedUpstreams.forEach(upstream => {
if (!nodeToNodeSubscriptions.has(upstream)) {
return;
}
const existing = nullthrows(nodeToNodeSubscriptions.get(upstream));
existing.delete(downstream);
if (existing.size === 0) {
nodeToNodeSubscriptions.delete(upstream);
}
});
}
});
}
function saveDependencyMapToStore(
dependencyMap: DependencyMap,
store: Store,
version: StateID,
): void {
const storeState = store.getState();
if (
!(
version === storeState.currentTree.version ||
version === storeState.nextTree?.version ||
version === storeState.previousTree?.version
)
) {
recoverableViolation(
'Tried to save dependencies to a discarded tree',
'recoil',
);
}
// Merge the dependencies discovered into the store's dependency map
// for the version that was read:
const graph = store.getGraph(version);
mergeDependencyMapIntoGraph(dependencyMap, graph);
// If this version is not the latest version, also write these dependencies
// into later versions if they don't already have their own:
if (version === storeState.previousTree?.version) {
const currentGraph = store.getGraph(storeState.currentTree.version);
mergeDependencyMapIntoGraph(dependencyMap, currentGraph, graph);
}
if (
version === storeState.previousTree?.version ||
version === storeState.currentTree.version
) {
const nextVersion = storeState.nextTree?.version;
if (nextVersion !== undefined) {
const nextGraph = store.getGraph(nextVersion);
mergeDependencyMapIntoGraph(dependencyMap, nextGraph, graph);
}
}
}
function mergeDepsIntoDependencyMap(
from: DependencyMap,
into: DependencyMap,
): void {
from.forEach((upstreamDeps, downstreamNode) => {
if (!into.has(downstreamNode)) {
into.set(downstreamNode, new Set());
}
const deps = nullthrows(into.get(downstreamNode));
upstreamDeps.forEach(dep => deps.add(dep));
});
}
function addToDependencyMap(
downstream: NodeKey,
upstream: NodeKey,
dependencyMap: DependencyMap,
): void {
if (!dependencyMap.has(downstream)) {
dependencyMap.set(downstream, new Set());
}
nullthrows(dependencyMap.get(downstream)).add(upstream);
}
module.exports = {
addToDependencyMap,
cloneGraph,
graph: makeGraph,
mergeDepsIntoDependencyMap,
saveDependencyMapToStore,
};