@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
174 lines (138 loc) • 5.71 kB
JavaScript
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { isSelector, isValue } from "./processors";
import { mapKeys } from "./typeddash";
export class DependencyManager {
constructor(registerValues) {
_defineProperty(this, "registerValues", void 0);
_defineProperty(this, "subscriptions", new Map());
_defineProperty(this, "unregisterDependantValues", null);
this.registerValues = registerValues;
}
/**
* Call to unsubscribe all value listeners from the given node based
* on the current list of subscriptions for the node. This function
* is typically called when the node is unmounted or when one or more
* properties have changed.
* @param node Node to unsubscribe value listeners from
*/
unsubscribeNode(node) {
const subscriptions = Array.from(this.subscriptions.values()).filter(p => p.nodes.has(node));
if (subscriptions) {
subscriptions.forEach(si => {
// Delete node from subscription
si.nodes.delete(node); // Remove subscription if there are no listeneres left on the value
if (si.nodes.size === 0) {
// There are no more nodes subscribing to this value, we can call
// unsubscribe on it.
if (!si.unsubscribe) {
throw new Error("Failed to unsubscribe to value subscription");
}
si.unsubscribe && si.unsubscribe(); // Remove from subscription states as well
const element = Array.from(this.subscriptions.entries()).find(_ref => {
let [_, sub] = _ref;
return sub === si;
});
if (!element) {
throw new Error("Failed to find value subscription");
}
if (!this.subscriptions.delete(element[0])) {
throw new Error("Failed to delete value subscription");
}
}
});
}
}
/**
* Adds listeners to the provided values so that the node is notified
* when a value changes. This is done in an optimized way so that this
* class only needs to listen to the value once and then forwards the
* change to the node and its listener. This method is typically called
* when the node is mounted and when one or more props on the node changes.
* @param node Node to subscribe to value changes for
* @param props Node's properties
*/
subscribeNode(node, props) {
// Get mutators from node's properties
const propSubscriptions = initializePropertySubscriptions(node, props);
if (propSubscriptions.length === 0) {
return;
} // Install all mutators for the node
propSubscriptions.forEach(ps => {
// Do we already have a state for this SkiaValue
let subscriptionState = this.subscriptions.get(ps.value);
if (!subscriptionState) {
// Let's create a new subscription state for the skia value
subscriptionState = {
nodes: new Map(),
unsubscribe: null
}; // Add single subscription to the new value
subscriptionState.unsubscribe = ps.value.addListener(v => {
subscriptionState.nodes.forEach(mutators => mutators.forEach(m => m(v)));
});
this.subscriptions.set(ps.value, subscriptionState);
} // subscription mutators
subscriptionState.nodes.set(node, propSubscriptions.filter(m => m.value === ps.value).map(m => m.mutator));
});
}
/**
* Called when the hosting container is mounted or updated. This ensures that we have
* a ref to the underlying SkiaView so that we can registers redraw listeners
* on values used in the current View automatically.
*/
update() {
// Remove any previous registrations
if (this.unregisterDependantValues) {
this.unregisterDependantValues();
} // Register redraw requests on the SkiaView for each unique value
this.unregisterDependantValues = this.registerValues(Array.from(this.subscriptions.keys()));
}
/**
* Called when the hosting container is unmounted or recreated. This ensures that we remove
* all subscriptions to Skia values so that we don't have any listeners left after
* the component is removed.
*/
remove() {
// 1) Unregister redraw requests
if (this.unregisterDependantValues) {
this.unregisterDependantValues();
this.unregisterDependantValues = null;
} // 2) Unregister nodes
Array.from(this.subscriptions.values()).forEach(si => {
Array.from(si.nodes.keys()).forEach(node => this.unsubscribeNode(node));
}); // 3) Clear the rest of the subscriptions
this.subscriptions.clear();
}
}
const initializePropertySubscriptions = (node, props) => {
const nodePropSubscriptions = [];
mapKeys(props).forEach(key => {
if (key === "children") {
return;
}
const propvalue = props[key];
if (isValue(propvalue)) {
// Subscribe to changes
nodePropSubscriptions.push({
value: propvalue,
mutator: v => {
node.setProp(key, v);
}
}); // Set initial value
node.setProp(key, propvalue.current);
} else if (isSelector(propvalue)) {
// Subscribe to changes
nodePropSubscriptions.push({
value: propvalue.value,
mutator: v => {
node.setProp(key, propvalue.selector(v));
}
}); // Set initial value
node.setProp(key, propvalue.selector(propvalue.value.current));
} else {
// Set initial value
node.setProp(key, propvalue);
}
});
return nodePropSubscriptions;
};
//# sourceMappingURL=DependencyManager.js.map