Remove dependencies' cycles (#187)

The main problems were with setValue and interpolate so I made them not available internally and exposed it with addition class. In internal operation InternalAnimatedValue is used and it's not causing dependencies' cycles.

Changed logic of exposing nodes. Now there's no need to import whole base.js for wrapped nodes.
This commit is contained in:
Michał Osadnik
2019-04-25 10:49:54 +02:00
committed by GitHub
parent bb066ed50f
commit c841c34d10
33 changed files with 251 additions and 229 deletions

View File

@@ -3,7 +3,7 @@ import Animation from './Animation';
import decay from './decay';
import { block, clockRunning, startClock, stopClock, cond } from '../base';
import Clock from '../core/AnimatedClock';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
class DecayAnimation extends Animation {
constructor(config) {

View File

@@ -1,4 +1,4 @@
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
import Animation from './Animation';
import SpringConfig from '../SpringConfig';
import spring from './spring';

View File

@@ -1,4 +1,4 @@
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
import timing from './timing';
import { block, clockRunning, startClock, stopClock, cond } from '../base';
import Clock from '../core/AnimatedClock';

View File

@@ -18,7 +18,7 @@ import {
greaterThan,
} from '../base';
import { min, abs } from '../derived';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
const MAX_STEPS_MS = 64;

View File

@@ -1,111 +1,16 @@
import AnimatedCond from './core/AnimatedCond';
import AnimatedSet from './core/AnimatedSet';
import AnimatedOperator from './core/AnimatedOperator';
import AnimatedStartClock from './core/AnimatedStartClock';
import AnimatedStopClock from './core/AnimatedStopClock';
import AnimatedClockTest from './core/AnimatedClockTest';
import AnimatedDebug from './core/AnimatedDebug';
import AnimatedCall from './core/AnimatedCall';
import AnimatedEvent from './core/AnimatedEvent';
import AnimatedAlways from './core/AnimatedAlways';
import AnimatedConcat from './core/AnimatedConcat';
import { adapt } from './utils';
function operator(name) {
return (...args) => new AnimatedOperator(name, args.map(adapt));
}
export const add = operator('add');
export const sub = operator('sub');
export const multiply = operator('multiply');
export const divide = operator('divide');
export const pow = operator('pow');
export const modulo = operator('modulo');
export const sqrt = operator('sqrt');
export const sin = operator('sin');
export const cos = operator('cos');
export const tan = operator('tan');
export const acos = operator('acos');
export const asin = operator('asin');
export const atan = operator('atan');
export const exp = operator('exp');
export const round = operator('round');
export const lessThan = operator('lessThan');
export const eq = operator('eq');
export const greaterThan = operator('greaterThan');
export const lessOrEq = operator('lessOrEq');
export const greaterOrEq = operator('greaterOrEq');
export const neq = operator('neq');
export const and = operator('and');
export const or = operator('or');
export const defined = operator('defined');
export const not = operator('not');
export const set = function(what, value) {
return new AnimatedSet(what, adapt(value));
};
export const cond = function(cond, ifBlock, elseBlock) {
return new AnimatedCond(
adapt(cond),
adapt(ifBlock),
elseBlock === undefined ? undefined : adapt(elseBlock)
);
};
export const block = function(items) {
return adapt(items);
};
export const call = function(args, func) {
return new AnimatedCall(args, func);
};
export const debug = function(message, value) {
if (__DEV__) {
const runningInRemoteDebugger = typeof atob !== 'undefined';
// hack to detect if app is running in remote debugger
// https://stackoverflow.com/questions/39022216
const runningInExpoShell =
global.Expo && global.Expo.Constants.appOwnership !== 'standalone';
if (runningInRemoteDebugger || runningInExpoShell) {
// When running in expo or remote debugger we use JS console.log to output variables
// otherwise we output to the native console using native debug node
return block([
call([value], ([a]) => console.log(`${message} ${a}`)),
value,
]);
} else {
return new AnimatedDebug(message, adapt(value));
}
}
// Debugging is disabled in PROD
return value;
};
export const startClock = function(clock) {
return new AnimatedStartClock(clock);
};
export const always = function(item) {
return new AnimatedAlways(item);
};
export const concat = function(...args) {
return new AnimatedConcat(args.map(adapt));
};
export const stopClock = function(clock) {
return new AnimatedStopClock(clock);
};
export const clockRunning = function(clock) {
return new AnimatedClockTest(clock);
};
export const event = function(argMapping, config) {
return new AnimatedEvent(argMapping, config);
};
export { createAnimatedCond as cond } from './core/AnimatedCond';
export { createAnimatedSet as set } from './core/AnimatedSet';
export {
createAnimatedStartClock as startClock,
} from './core/AnimatedStartClock';
export { createAnimatedStopClock as stopClock } from './core/AnimatedStopClock';
export {
createAnimatedClockTest as clockRunning,
} from './core/AnimatedClockTest';
export { createAnimatedDebug as debug } from './core/AnimatedDebug';
export { createAnimatedCall as call } from './core/AnimatedCall';
export { createAnimatedEvent as event } from './core/AnimatedEvent';
export { createAnimatedAlways as always } from './core/AnimatedAlways';
export { createAnimatedConcat as concat } from './core/AnimatedConcat';
export { createAnimatedBlock as block, adapt } from './core/AnimatedBlock';
export * from './operators';

View File

@@ -1,6 +1,6 @@
import AnimatedNode from './AnimatedNode';
export default class AnimatedAlways extends AnimatedNode {
class AnimatedAlways extends AnimatedNode {
_what;
constructor(what) {
@@ -12,3 +12,7 @@ export default class AnimatedAlways extends AnimatedNode {
return 0;
}
}
export function createAnimatedAlways(item) {
return new AnimatedAlways(item);
}

View File

@@ -1,4 +1,4 @@
import { val } from '../utils';
import { val } from '../val';
import AnimatedNode from './AnimatedNode';
// These values are established by empiricism with tests (tradeoff: performance VS precision)

View File

@@ -1,7 +1,8 @@
import AnimatedNode from './AnimatedNode';
import { val } from '../utils';
import { val } from '../val';
import InternalAnimatedValue from './InternalAnimatedValue';
export default class AnimatedBlock extends AnimatedNode {
class AnimatedBlock extends AnimatedNode {
_array;
constructor(array) {
@@ -17,3 +18,24 @@ export default class AnimatedBlock extends AnimatedNode {
return result;
}
}
export function createAnimatedBlock(items) {
return adapt(items);
}
function nodify(v) {
if (typeof v === 'object' && v.__isProxy) {
if (!v.__val) {
v.__val = new InternalAnimatedValue(0);
}
return v.__val;
}
// TODO: cache some typical static values (e.g. 0, 1, -1)
return v instanceof AnimatedNode ? v : new InternalAnimatedValue(v);
}
export function adapt(v) {
return Array.isArray(v)
? new AnimatedBlock(v.map(node => adapt(node)))
: nodify(v);
}

View File

@@ -1,5 +1,5 @@
import ReanimatedEventEmitter from '../ReanimatedEventEmitter';
import { val } from '../utils';
import { val } from '../val';
import AnimatedNode from './AnimatedNode';
const NODE_MAPPING = new Map();
@@ -9,7 +9,7 @@ function listener(data) {
node && node._callback(data.args);
}
export default class AnimatedCall extends AnimatedNode {
class AnimatedCall extends AnimatedNode {
_callback;
_args;
@@ -40,3 +40,7 @@ export default class AnimatedCall extends AnimatedNode {
return 0;
}
}
export function createAnimatedCall(args, func) {
return new AnimatedCall(args, func);
}

View File

@@ -1,8 +1,8 @@
import AnimatedValue from './AnimatedValue';
import InternalAnimatedValue from './AnimatedValue';
import AnimatedNode from './AnimatedNode';
import { val } from '../utils';
import { val } from '../val';
class AnimatedMainClock extends AnimatedValue {
class AnimatedMainClock extends InternalAnimatedValue {
_frameCallback;
constructor() {

View File

@@ -2,7 +2,7 @@ import AnimatedNode from './AnimatedNode';
import AnimatedClock from './AnimatedClock';
import invariant from 'fbjs/lib/invariant';
export default class AnimatedClockTest extends AnimatedNode {
class AnimatedClockTest extends AnimatedNode {
_clockNode;
constructor(clockNode) {
@@ -18,3 +18,7 @@ export default class AnimatedClockTest extends AnimatedNode {
return this._clockNode.isStarted() ? 1 : 0;
}
}
export function createAnimatedClockTest(clock) {
return new AnimatedClockTest(clock);
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import AnimatedAlways from './AnimatedAlways';
import { createAnimatedAlways } from './AnimatedAlways';
import AnimatedNode from './AnimatedNode';
class Code extends React.Component {
@@ -33,7 +33,7 @@ class Code extends React.Component {
);
}
this.always = new AnimatedAlways(nodeExec || nodeChildren);
this.always = createAnimatedAlways(nodeExec || nodeChildren);
this.always.__attach();
}

View File

@@ -1,7 +1,12 @@
import AnimatedNode from './AnimatedNode';
import { adapt } from '../core/AnimatedBlock';
export default class AnimatedConcat extends AnimatedNode {
class AnimatedConcat extends AnimatedNode {
constructor(input) {
super({ type: 'concat', input: input.map(n => n.__nodeID) }, input);
}
}
export function createAnimatedConcat(...args) {
return new AnimatedConcat(args.map(adapt));
}

View File

@@ -1,7 +1,8 @@
import { val } from '../utils';
import { val } from '../val';
import AnimatedNode from './AnimatedNode';
import { adapt } from '../core/AnimatedBlock';
export default class AnimatedCond extends AnimatedNode {
class AnimatedCond extends AnimatedNode {
_condition;
_ifBlock;
_elseBlock;
@@ -29,3 +30,11 @@ export default class AnimatedCond extends AnimatedNode {
}
}
}
export function createAnimatedCond(cond, ifBlock, elseBlock) {
return new AnimatedCond(
adapt(cond),
adapt(ifBlock),
elseBlock === undefined ? undefined : adapt(elseBlock)
);
}

View File

@@ -1,7 +1,9 @@
import { val } from '../utils';
import { val } from '../val';
import AnimatedNode from './AnimatedNode';
import { createAnimatedBlock as block, adapt } from './AnimatedBlock';
import { createAnimatedCall as call } from './AnimatedCall';
export default class AnimatedDebug extends AnimatedNode {
class AnimatedDebug extends AnimatedNode {
_message;
_value;
@@ -17,3 +19,27 @@ export default class AnimatedDebug extends AnimatedNode {
return value;
}
}
export function createAnimatedDebug(message, value) {
if (__DEV__) {
const runningInRemoteDebugger = typeof atob !== 'undefined';
// hack to detect if app is running in remote debugger
// https://stackoverflow.com/questions/39022216
const runningInExpoShell =
global.Expo && global.Expo.Constants.appOwnership !== 'standalone';
if (runningInRemoteDebugger || runningInExpoShell) {
// When running in expo or remote debugger we use JS console.log to output variables
// otherwise we output to the native console using native debug node
return block([
call([value], ([a]) => console.log(`${message} ${a}`)),
value,
]);
} else {
return new AnimatedDebug(message, adapt(value));
}
}
// Debugging is disabled in PROD
return value;
}

View File

@@ -2,8 +2,8 @@ import { findNodeHandle } from 'react-native';
import ReanimatedModule from '../ReanimatedModule';
import AnimatedNode from './AnimatedNode';
import AnimatedValue from './AnimatedValue';
import AnimatedAlways from './AnimatedAlways';
import InternalAnimatedValue from './AnimatedValue';
import { createAnimatedAlways } from './AnimatedAlways';
import invariant from 'fbjs/lib/invariant';
import createEventObjectProxyPolyfill from './createEventObjectProxyPolyfill';
@@ -15,13 +15,13 @@ function sanitizeArgMapping(argMapping) {
const alwaysNodes = [];
const traverse = (value, path) => {
if (value instanceof AnimatedValue) {
if (value instanceof InternalAnimatedValue) {
eventMappings.push(path.concat(value.__nodeID));
} else if (typeof value === 'object' && value.__val) {
eventMappings.push(path.concat(value.__val.__nodeID));
} else if (typeof value === 'function') {
const node = new AnimatedValue(0);
alwaysNodes.push(new AnimatedAlways(value(node)));
const node = new InternalAnimatedValue(0);
alwaysNodes.push(createAnimatedAlways(value(node)));
eventMappings.push(path.concat(node.__nodeID));
} else if (typeof value === 'object') {
for (const key in value) {
@@ -61,7 +61,7 @@ function sanitizeArgMapping(argMapping) {
typeof Proxy === 'function'
? new Proxy({}, proxyHandler)
: createEventObjectProxyPolyfill();
alwaysNodes.push(new AnimatedAlways(ev(proxy)));
alwaysNodes.push(createAnimatedAlways(ev(proxy)));
traverse(proxy, []);
}
@@ -99,3 +99,7 @@ export default class AnimatedEvent extends AnimatedNode {
this.__detach();
}
}
export function createAnimatedEvent(argMapping, config) {
return new AnimatedEvent(argMapping, config);
}

View File

@@ -1,7 +1,8 @@
import AnimatedNode from './AnimatedNode';
import { val } from '../utils';
import { val } from '../val';
import invariant from 'fbjs/lib/invariant';
import { adapt } from '../core/AnimatedBlock';
function reduce(fn) {
return input => input.reduce((a, b) => fn(val(a), val(b)));
@@ -53,7 +54,7 @@ const OPERATIONS = {
neq: infix((a, b) => a != b),
};
export default class AnimatedOperator extends AnimatedNode {
class AnimatedOperator extends AnimatedNode {
_input;
_op;
_operation;
@@ -75,3 +76,7 @@ export default class AnimatedOperator extends AnimatedNode {
return this._operation(this._input);
}
}
export function createAnimatedOperator(name) {
return (...args) => new AnimatedOperator(name, args.map(adapt));
}

View File

@@ -1,7 +1,8 @@
import AnimatedNode from './AnimatedNode';
import { val } from '../utils';
import { val } from '../val';
import { adapt } from '../core/AnimatedBlock';
export default class AnimatedSet extends AnimatedNode {
class AnimatedSet extends AnimatedNode {
_what;
_value;
@@ -17,3 +18,7 @@ export default class AnimatedSet extends AnimatedNode {
return newValue;
}
}
export function createAnimatedSet(what, value) {
return new AnimatedSet(what, adapt(value));
}

View File

@@ -2,7 +2,7 @@ import AnimatedNode from './AnimatedNode';
import AnimatedClock from './AnimatedClock';
import invariant from 'fbjs/lib/invariant';
export default class AnimatedStartClock extends AnimatedNode {
class AnimatedStartClock extends AnimatedNode {
_clockNode;
constructor(clockNode) {
@@ -19,3 +19,7 @@ export default class AnimatedStartClock extends AnimatedNode {
return 0;
}
}
export function createAnimatedStartClock(clock) {
return new AnimatedStartClock(clock);
}

View File

@@ -2,7 +2,7 @@ import AnimatedNode from './AnimatedNode';
import AnimatedClock from './AnimatedClock';
import invariant from 'fbjs/lib/invariant';
export default class AnimatedStopClock extends AnimatedNode {
class AnimatedStopClock extends AnimatedNode {
_clockNode;
constructor(clockNode) {
@@ -19,3 +19,7 @@ export default class AnimatedStopClock extends AnimatedNode {
return 0;
}
}
export function createAnimatedStopClock(clock) {
return new AnimatedStopClock(clock);
}

View File

@@ -1,56 +1,10 @@
import AnimatedNode from './AnimatedNode';
import { set } from '../base';
import { val } from '../utils';
import { evaluateOnce } from '../derived/evaluateOnce';
import { createAnimatedSet as set } from '../core/AnimatedSet';
import interpolate from '../derived/interpolate';
import ReanimatedModule from '../ReanimatedModule';
function sanitizeValue(value) {
return value === null || value === undefined || typeof value === 'string'
? value
: Number(value);
}
export default class AnimatedValue extends AnimatedNode {
constructor(value) {
super({ type: 'value', value: sanitizeValue(value) });
this._startingValue = this._value = value;
this._animation = null;
}
__detach() {
ReanimatedModule.getValue(
this.__nodeID,
val => (this.__nodeConfig.value = val)
);
this.__detachAnimation(this._animation);
super.__detach();
}
__detachAnimation(animation) {
animation && animation.__detach();
if (this._animation === animation) {
this._animation = null;
}
}
__attachAnimation(animation) {
this.__detachAnimation(this._animation);
this._animation = animation;
}
__onEvaluate() {
if (this.__inputNodes && this.__inputNodes.length) {
this.__inputNodes.forEach(val);
}
return this._value + this._offset;
}
_updateValue(value) {
this._value = value;
this.__forceUpdateCache(value);
}
import InternalAnimatedValue from './InternalAnimatedValue';
import { evaluateOnce } from '../derived/evaluateOnce';
// Animated value wrapped with extra methods for omit cycle of dependencies
export default class AnimatedValue extends InternalAnimatedValue {
setValue(value) {
this.__detachAnimation(this._animation);
evaluateOnce(set(this, value), this);

View File

@@ -0,0 +1,54 @@
import AnimatedNode from './AnimatedNode';
import { val } from '../val';
import ReanimatedModule from '../ReanimatedModule';
function sanitizeValue(value) {
return value === null || value === undefined || typeof value === 'string'
? value
: Number(value);
}
/**
* This class has been made internal in order to omit dependencies' cycles which
* were caused by imperative setValue and interpolate they are currently exposed with AnimatedValue.js
*/
export default class InternalAnimatedValue extends AnimatedNode {
constructor(value) {
super({ type: 'value', value: sanitizeValue(value) });
this._startingValue = this._value = value;
this._animation = null;
}
__detach() {
ReanimatedModule.getValue(
this.__nodeID,
val => (this.__nodeConfig.value = val)
);
this.__detachAnimation(this._animation);
super.__detach();
}
__detachAnimation(animation) {
animation && animation.__detach();
if (this._animation === animation) {
this._animation = null;
}
}
__attachAnimation(animation) {
this.__detachAnimation(this._animation);
this._animation = animation;
}
__onEvaluate() {
if (this.__inputNodes && this.__inputNodes.length) {
this.__inputNodes.forEach(val);
}
return this._value + this._offset;
}
_updateValue(value) {
this._value = value;
this.__forceUpdateCache(value);
}
}

View File

@@ -1,5 +1,5 @@
import { set, add } from '../base';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
export default function acc(v) {
const acc = new AnimatedValue(0);

View File

@@ -1,5 +1,5 @@
import { cond, block, defined, sub, set } from '../base';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
export default function diff(v) {
const stash = new AnimatedValue(0);

View File

@@ -1,5 +1,5 @@
import { cond, defined, set, add } from '../base';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
import min from './min';
import max from './max';
import diff from './diff';

View File

@@ -1,5 +1,8 @@
import AnimatedValue from '../core/AnimatedValue';
import { call, always, cond, set } from '../base';
import AnimatedValue from '../core/InternalAnimatedValue';
import { createAnimatedSet as set } from '../core/AnimatedSet';
import { createAnimatedCall as call } from '../core/AnimatedCall';
import { createAnimatedAlways as always } from '../core/AnimatedAlways';
import { createAnimatedCond as cond } from '../core/AnimatedCond';
/**
* evaluate given node and notify children
@@ -7,6 +10,7 @@ import { call, always, cond, set } from '../base';
* @param input - nodes (or one node) representing values which states input for node.
* @param callback - after callback
*/
export function evaluateOnce(node, input = [], callback) {
if (!Array.isArray(input)) {
input = [input];

View File

@@ -1,12 +1,13 @@
import {
cond,
lessThan,
multiply,
sub,
add,
divide,
greaterThan,
} from '../base';
} from '../operators';
import { createAnimatedCond as cond } from '../core/AnimatedCond';
import invariant from 'fbjs/lib/invariant';
import AnimatedNode from '../core/AnimatedNode';

View File

@@ -1,5 +1,5 @@
import { cond, lessThan } from '../base';
import { adapt } from '../utils';
import { adapt } from '../core/AnimatedBlock';
export default function max(a, b) {
a = adapt(a);

View File

@@ -1,5 +1,5 @@
import { cond, lessThan } from '../base';
import { adapt } from '../utils';
import { adapt } from '../core/AnimatedBlock';
export default function min(a, b) {
a = adapt(a);

View File

@@ -1,5 +1,5 @@
import { block, cond, defined, neq, not, set } from '../base';
import AnimatedValue from '../core/AnimatedValue';
import AnimatedValue from '../core/InternalAnimatedValue';
export default function onChange(value, action) {
const prevValue = new AnimatedValue();

29
src/operators.js Normal file
View File

@@ -0,0 +1,29 @@
import { createAnimatedOperator } from './core/AnimatedOperator';
const operator = createAnimatedOperator;
export const add = operator('add');
export const sub = operator('sub');
export const multiply = operator('multiply');
export const divide = operator('divide');
export const pow = operator('pow');
export const modulo = operator('modulo');
export const sqrt = operator('sqrt');
export const sin = operator('sin');
export const cos = operator('cos');
export const exp = operator('exp');
export const round = operator('round');
export const lessThan = operator('lessThan');
export const eq = operator('eq');
export const greaterThan = operator('greaterThan');
export const lessOrEq = operator('lessOrEq');
export const greaterOrEq = operator('greaterOrEq');
export const neq = operator('neq');
export const and = operator('and');
export const or = operator('or');
export const defined = operator('defined');
export const not = operator('not');
export const tan = operator('tan');
export const acos = operator('acos');
export const asin = operator('asin');
export const atan = operator('atan');

View File

@@ -1,24 +0,0 @@
import AnimatedBlock from './core/AnimatedBlock';
import AnimatedNode from './core/AnimatedNode';
import AnimatedValue from './core/AnimatedValue';
function nodify(v) {
if (typeof v === 'object' && v.__isProxy) {
if (!v.__val) {
v.__val = new AnimatedValue(0);
}
return v.__val;
}
// TODO: cache some typical static values (e.g. 0, 1, -1)
return v instanceof AnimatedNode ? v : new AnimatedValue(v);
}
export function adapt(v) {
return Array.isArray(v)
? new AnimatedBlock(v.map(node => adapt(node)))
: nodify(v);
}
export function val(v) {
return v && v.__getValue ? v.__getValue() : v || 0;
}

3
src/val.js Normal file
View File

@@ -0,0 +1,3 @@
export function val(v) {
return v && v.__getValue ? v.__getValue() : v || 0;
}