diff --git a/Example/test/index.js b/Example/test/index.js index 059cae1dce0..88b6947529e 100644 --- a/Example/test/index.js +++ b/Example/test/index.js @@ -21,8 +21,64 @@ const { Value, Clock, event, + proc, } = Animated; +const betterSpring = proc( + ( + finished, + velocity, + position, + time, + prevPosition, + toValue, + damping, + mass, + stiffness, + overshootClamping, + restSpeedThreshold, + restDisplacementThreshold, + clock + ) => + spring( + clock, + { + finished, + velocity, + position, + time, + prevPosition, + }, + { + toValue, + damping, + mass, + stiffness, + overshootClamping, + restDisplacementThreshold, + restSpeedThreshold, + } + ) +); + +function springFill(clock, state, config) { + return betterSpring( + state.finished, + state.velocity, + state.position, + state.time, + new Value(0), + config.toValue, + config.damping, + config.mass, + config.stiffness, + config.overshootClamping, + config.restSpeedThreshold, + config.restDisplacementThreshold, + clock + ); +} + function runSpring(clock, value, dest) { const state = { finished: new Value(0), @@ -34,7 +90,7 @@ function runSpring(clock, value, dest) { const config = { toValue: new Value(0), damping: 7, - mass: 1, + mass: 5, stiffness: 121.6, overshootClamping: false, restSpeedThreshold: 0.001, @@ -50,7 +106,7 @@ function runSpring(clock, value, dest) { set(config.toValue, dest), startClock(clock), ]), - spring(clock, state, config), + springFill(clock, state, config), cond(state.finished, debug('stop clock', stopClock(clock))), state.position, ]); @@ -92,10 +148,11 @@ export default class Example extends Component { // const transX = new Value(0); const clock = new Clock(); // const twenty = new Value(20); - // const thirty = new Value(30); - // this._transX = cond(new Value(0), twenty, multiply(3, thirty)); - this._transX = runTiming(clock, -120, 120); + this.t = Array.from(Array(40)).map(() => + runSpring(new Clock(), Math.random() * -200, Math.random() * 200) + ); } + componentDidMount() { // Animated.spring(this._transX, { // duration: 300, @@ -103,18 +160,22 @@ export default class Example extends Component { // toValue: 150, // }).start(); } + render() { return ( - - + + {Array.from(Array(40)).map((_, i) => ( + + ))} ); } } -const BOX_SIZE = 100; +const BOX_SIZE = 10; const styles = StyleSheet.create({ container: { @@ -129,6 +190,6 @@ const styles = StyleSheet.create({ borderColor: '#F5FCFF', alignSelf: 'center', backgroundColor: 'plum', - margin: BOX_SIZE / 2, + margin: 2, }, }); diff --git a/README.md b/README.md index dc792817b7f..c3fb3251eeb 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,24 @@ Returns an accumulated value of the given node. This node stores a sum of all ev Works the same way as with the original `Animated` library. +--- +### `proc` + +Returns a callable function node that can be used to define expressions that can be called from other nodes. + +Example: +´´´js +// Global constant +const myProc = proc((a, b) => multiply(a,b)); + +// In your component +const style = { width: proc(10, 10 )}; +´´´ + +A proc node should be declared as a global constant in your code and not recreated from inside components. + +It is not possible to reference nodes that are not passed as parameters. + --- ### `interpolate` ```js diff --git a/android/src/main/java/com/swmansion/reanimated/NodesManager.java b/android/src/main/java/com/swmansion/reanimated/NodesManager.java index f10e4139c6c..9974c64fcb2 100644 --- a/android/src/main/java/com/swmansion/reanimated/NodesManager.java +++ b/android/src/main/java/com/swmansion/reanimated/NodesManager.java @@ -37,6 +37,9 @@ import com.swmansion.reanimated.nodes.StyleNode; import com.swmansion.reanimated.nodes.TransformNode; import com.swmansion.reanimated.nodes.ValueNode; +import com.swmansion.reanimated.nodes.ParamNode; +import com.swmansion.reanimated.nodes.FunctionNode; +import com.swmansion.reanimated.nodes.CallFuncNode; import java.util.ArrayList; import java.util.Collections; @@ -264,6 +267,12 @@ public void createNode(int nodeID, ReadableMap config) { node = new AlwaysNode(nodeID, config, this); } else if ("concat".equals(type)) { node = new ConcatNode(nodeID, config, this); + } else if ("param".equals(type)) { + node = new ParamNode(nodeID, config, this); + } else if ("func".equals(type)) { + node = new FunctionNode(nodeID, config, this); + } else if ("callfunc".equals(type)) { + node = new CallFuncNode(nodeID, config, this); } else { throw new JSApplicationIllegalArgumentException("Unsupported node type: " + type); } diff --git a/android/src/main/java/com/swmansion/reanimated/UpdateContext.java b/android/src/main/java/com/swmansion/reanimated/UpdateContext.java index 3b94750a078..4a1891cf894 100644 --- a/android/src/main/java/com/swmansion/reanimated/UpdateContext.java +++ b/android/src/main/java/com/swmansion/reanimated/UpdateContext.java @@ -7,6 +7,7 @@ public class UpdateContext { public long updateLoopID = 0; + public String callID = ""; public final ArrayList updatedNodes = new ArrayList<>(); } diff --git a/android/src/main/java/com/swmansion/reanimated/nodes/CallFuncNode.java b/android/src/main/java/com/swmansion/reanimated/nodes/CallFuncNode.java new file mode 100644 index 00000000000..f68a99c9687 --- /dev/null +++ b/android/src/main/java/com/swmansion/reanimated/nodes/CallFuncNode.java @@ -0,0 +1,48 @@ +package com.swmansion.reanimated.nodes; + +import com.facebook.react.bridge.ReadableMap; +import com.swmansion.reanimated.NodesManager; +import com.swmansion.reanimated.Utils; + +public class CallFuncNode extends Node { + + private String mPreviousCallID; + private final int mWhatNodeID; + private final int[] mArgs; + private final int[] mParams; + + public CallFuncNode(int nodeID, ReadableMap config, NodesManager nodesManager) { + super(nodeID, config, nodesManager); + mWhatNodeID = config.getInt("what"); + mParams = Utils.processIntArray(config.getArray("params")); + mArgs = Utils.processIntArray(config.getArray("args")); + } + + private void beginContext() { + mPreviousCallID = mNodesManager.updateContext.callID; + mNodesManager.updateContext.callID = mNodesManager.updateContext.callID + '/' + String.valueOf(mNodeID); + for (int i = 0; i < mParams.length; i++) { + int paramId = mParams[i]; + ParamNode paramNode = mNodesManager.findNodeById(paramId, ParamNode.class); + paramNode.beginContext(mArgs[i], mPreviousCallID); + } + } + + private void endContext() { + for (int i = 0; i < mParams.length; i++) { + int paramId = mParams[i]; + ParamNode paramNode = mNodesManager.findNodeById(paramId, ParamNode.class); + paramNode.endContext(); + } + mNodesManager.updateContext.callID = mPreviousCallID; + } + + @Override + protected Object evaluate() { + beginContext(); + Node whatNode = mNodesManager.findNodeById(mWhatNodeID, Node.class); + Object retVal = whatNode.value(); + endContext(); + return retVal; + } +} diff --git a/android/src/main/java/com/swmansion/reanimated/nodes/FunctionNode.java b/android/src/main/java/com/swmansion/reanimated/nodes/FunctionNode.java new file mode 100644 index 00000000000..9970a94d78b --- /dev/null +++ b/android/src/main/java/com/swmansion/reanimated/nodes/FunctionNode.java @@ -0,0 +1,20 @@ +package com.swmansion.reanimated.nodes; + +import com.facebook.react.bridge.ReadableMap; +import com.swmansion.reanimated.NodesManager; + +public class FunctionNode extends Node { + + private final int mWhatNodeID; + + public FunctionNode(int nodeID, ReadableMap config, NodesManager nodesManager) { + super(nodeID, config, nodesManager); + mWhatNodeID = config.getInt("what"); + } + + @Override + protected Object evaluate() { + Node what = mNodesManager.findNodeById(mWhatNodeID, Node.class); + return what.value(); + } +} diff --git a/android/src/main/java/com/swmansion/reanimated/nodes/Node.java b/android/src/main/java/com/swmansion/reanimated/nodes/Node.java index ac58ce19318..9e6e11c2740 100644 --- a/android/src/main/java/com/swmansion/reanimated/nodes/Node.java +++ b/android/src/main/java/com/swmansion/reanimated/nodes/Node.java @@ -1,13 +1,17 @@ package com.swmansion.reanimated.nodes; +import android.util.SparseArray; + import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.swmansion.reanimated.NodesManager; import com.swmansion.reanimated.UpdateContext; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Stack; @@ -21,13 +25,14 @@ public abstract class Node { protected final int mNodeID; protected final NodesManager mNodesManager; - private final UpdateContext mUpdateContext; + protected final UpdateContext mUpdateContext; - private long mLastLoopID = -1; - private @Nullable Object mMemoizedValue; + private final Map mLastLoopID = new HashMap<>(); + private final Map mMemoizedValue = new HashMap<>(); private @Nullable List mChildren; /* lazy-initialized when a child is added */ public Node(int nodeID, @Nullable ReadableMap config, NodesManager nodesManager) { + mLastLoopID.put("", 1L); mNodeID = nodeID; mNodesManager = nodesManager; mUpdateContext = nodesManager.updateContext; @@ -36,11 +41,14 @@ public Node(int nodeID, @Nullable ReadableMap config, NodesManager nodesManager) protected abstract @Nullable Object evaluate(); public final @Nullable Object value() { - if (mLastLoopID < mUpdateContext.updateLoopID) { - mLastLoopID = mUpdateContext.updateLoopID; - return (mMemoizedValue = evaluate()); + if (!mLastLoopID.containsKey(mUpdateContext.callID) || mLastLoopID.get(mUpdateContext.callID) < mUpdateContext.updateLoopID) { + mLastLoopID.put(mUpdateContext.callID, mUpdateContext.updateLoopID); + Object result = evaluate(); + mMemoizedValue.put(mUpdateContext.callID, result); + + return result; } - return mMemoizedValue; + return mMemoizedValue.get(mUpdateContext.callID); } /** @@ -83,12 +91,12 @@ protected void markUpdated() { } protected final void dangerouslyRescheduleEvaluate() { - mLastLoopID = -1; + mLastLoopID.put(mUpdateContext.callID, -1L); markUpdated(); } protected final void forceUpdateMemoizedValue(Object value) { - mMemoizedValue = value; + mMemoizedValue.put(mUpdateContext.callID, value); markUpdated(); } diff --git a/android/src/main/java/com/swmansion/reanimated/nodes/ParamNode.java b/android/src/main/java/com/swmansion/reanimated/nodes/ParamNode.java new file mode 100644 index 00000000000..6a8c9994565 --- /dev/null +++ b/android/src/main/java/com/swmansion/reanimated/nodes/ParamNode.java @@ -0,0 +1,47 @@ +package com.swmansion.reanimated.nodes; + +import com.facebook.react.bridge.ReadableMap; +import com.swmansion.reanimated.NodesManager; + +import java.util.Stack; + +public class ParamNode extends ValueNode { + + private final Stack mArgsStack; + private String mPrevCallID; + + public ParamNode(int nodeID, ReadableMap config, NodesManager nodesManager) { + super(nodeID, config, nodesManager); + mArgsStack = new Stack<>(); + } + + @Override + public void setValue(Object value) { + Node node = mNodesManager.findNodeById(mArgsStack.peek(), Node.class); + String callID = mUpdateContext.callID; + mUpdateContext.callID = mPrevCallID; + ((ValueNode) node).setValue(value); + mUpdateContext.callID = callID; + } + + public void beginContext(Integer ref, String prevCallID) { + mPrevCallID = prevCallID; + mArgsStack.push(ref); + } + + + public void endContext() { + mArgsStack.pop(); + } + + + @Override + protected Object evaluate() { + String callID = mUpdateContext.callID; + mUpdateContext.callID = mPrevCallID; + Node node = mNodesManager.findNodeById(mArgsStack.peek(), Node.class); + Object val = node.value(); + mUpdateContext.callID = callID; + return val; + } +} diff --git a/ios/Nodes/REACallFuncNode.h b/ios/Nodes/REACallFuncNode.h new file mode 100644 index 00000000000..45027240bb7 --- /dev/null +++ b/ios/Nodes/REACallFuncNode.h @@ -0,0 +1,7 @@ + +#import "REANode.h" + +@interface REACallFuncNode : REANode + +@end + diff --git a/ios/Nodes/REACallFuncNode.m b/ios/Nodes/REACallFuncNode.m new file mode 100644 index 00000000000..9b9e954b9aa --- /dev/null +++ b/ios/Nodes/REACallFuncNode.m @@ -0,0 +1,65 @@ + + +#import "REACallFuncNode.h" +#import "REAFunctionNode.h" +#import "REAParamNode.h" +#import "REANodesManager.h" + +@implementation REACallFuncNode { + NSNumber *_whatNodeID; + NSArray *_args; + NSArray *_params; + NSString* _prevCallID; +} + +- (instancetype)initWithID:(REANodeID)nodeID config:(NSDictionary *)config +{ + if ((self = [super initWithID:nodeID config:config])) { + _whatNodeID = config[@"what"]; + _args = config[@"args"]; + _params = config[@"params"]; + _prevCallID = NULL; + } + return self; +} + +- (void)beginContext +{ + // To ensure that functions can be called multiple times in the same animation frame + // (functions might have different parameters and might be called multiple times) + // we inform the current update context about where we are called from by setting the + // current call id - this will ensure that memoization is correct for function nodes. + _prevCallID = self.updateContext.callID; + self.updateContext.callID = [NSString stringWithFormat:@"%@/%@", self.updateContext.callID, [self.nodeID stringValue]]; + + // A CallFuncNode has a reference to a function node which holds the node graph that should + // be updated. A Function node has a list of ParamNodes which are basically nodes that can + // reference other nodes. When we start a new function call we update the parameter nodes + // with the current arguments: + for (NSUInteger i = 0; i < _params.count; i++) { + NSNumber *paramID = [_params objectAtIndex:i]; + REAParamNode *param = (REAParamNode *)[self.nodesManager findNodeByID:paramID]; + [param beginContext:_args[i] prevCallID:_prevCallID]; + } +} + +- (void)endContext +{ + for (NSUInteger i = 0; i < _params.count; i++) { + NSNumber *paramID = [_params objectAtIndex:i]; + REAParamNode *param = (REAParamNode *)[self.nodesManager findNodeByID:paramID]; + [param endContext]; + } + self.updateContext.callID = _prevCallID; +} + +- (id)evaluate +{ + [self beginContext]; + REAFunctionNode *what = (REAFunctionNode *)[self.nodesManager findNodeByID:_whatNodeID]; + NSNumber *newValue = [what value]; + [self endContext]; + return newValue; +} + +@end diff --git a/ios/Nodes/REAFunctionNode.h b/ios/Nodes/REAFunctionNode.h new file mode 100644 index 00000000000..4b6ef737844 --- /dev/null +++ b/ios/Nodes/REAFunctionNode.h @@ -0,0 +1,7 @@ + +#import "REANode.h" + +@interface REAFunctionNode : REANode + +@end + diff --git a/ios/Nodes/REAFunctionNode.m b/ios/Nodes/REAFunctionNode.m new file mode 100644 index 00000000000..ad5c31052ea --- /dev/null +++ b/ios/Nodes/REAFunctionNode.m @@ -0,0 +1,24 @@ + +#import "REAFunctionNode.h" +#import "REAParamNode.h" +#import "REANodesManager.h" + +@implementation REAFunctionNode { + NSNumber *_nodeToBeEvaluated; +} + +- (instancetype)initWithID:(REANodeID)nodeID config:(NSDictionary *)config +{ + if ((self = [super initWithID:nodeID config:config])) { + _nodeToBeEvaluated = config[@"what"]; + } + return self; +} + +- (id)evaluate +{ + REANode *node = [self.nodesManager findNodeByID:_nodeToBeEvaluated]; + return [node value]; +} + +@end diff --git a/ios/Nodes/REANode.h b/ios/Nodes/REANode.h index 648580218a9..0874400dfad 100644 --- a/ios/Nodes/REANode.h +++ b/ios/Nodes/REANode.h @@ -12,6 +12,7 @@ typedef NSNumber* REANodeID; @end @interface REAUpdateContext : NSObject +@property (nonatomic) NSString* callID; @end @interface REANode : NSObject diff --git a/ios/Nodes/REANode.m b/ios/Nodes/REANode.m index ef5dd1e11d3..ebdffb7f279 100644 --- a/ios/Nodes/REANode.m +++ b/ios/Nodes/REANode.m @@ -6,7 +6,7 @@ @interface REAUpdateContext () @property (nonatomic, nonnull) NSMutableArray *updatedNodes; -@property (nonatomic) NSUInteger loopID; +@property (nonatomic) NSNumber* loopID; @end @@ -15,8 +15,9 @@ @implementation REAUpdateContext - (instancetype)init { if ((self = [super init])) { - _loopID = 1; + _loopID = [[NSNumber alloc] initWithInt:1]; _updatedNodes = [NSMutableArray new]; + _callID = @""; } return self; } @@ -26,8 +27,8 @@ - (instancetype)init @interface REANode () -@property (nonatomic) NSUInteger lastLoopID; -@property (nonatomic) id memoizedValue; +@property (nonatomic) NSMutableDictionary* lastLoopID; +@property (nonatomic) NSMutableDictionary* memoizedValue; @property (nonatomic, nullable) NSMutableArray *childNodes; @end @@ -38,7 +39,9 @@ - (instancetype)initWithID:(REANodeID)nodeID config:(NSDictionary { if ((self = [super init])) { _nodeID = nodeID; - _lastLoopID = 0; + _lastLoopID = [NSMutableDictionary dictionary]; + _memoizedValue = [NSMutableDictionary dictionary]; + _lastLoopID[@""] = @1; } return self; } @@ -47,13 +50,13 @@ - (instancetype)initWithID:(REANodeID)nodeID config:(NSDictionary - (void)dangerouslyRescheduleEvaluate { - _lastLoopID = 0; + _lastLoopID[self.updateContext.callID] = 0; [self markUpdated]; } - (void)forceUpdateMemoizedValue:(id)value { - _memoizedValue = value; + _memoizedValue[self.updateContext.callID] = value; [self markUpdated]; } @@ -64,11 +67,16 @@ - (id)evaluate - (id)value { - if (_lastLoopID < _updateContext.loopID) { - _lastLoopID = _updateContext.loopID; - return (_memoizedValue = [self evaluate]); + if (![_lastLoopID objectForKey:_updateContext.callID] || [[_lastLoopID objectForKey:_updateContext.callID] longValue] < [_updateContext.loopID longValue]) { + [_lastLoopID setObject:_updateContext.loopID forKey:_updateContext.callID]; + id val = [self evaluate]; + if (val == 0) { + val = [[NSNumber alloc] initWithInt:0]; + } + [_memoizedValue setObject:val forKey:_updateContext.callID]; + return val; } - return _memoizedValue; + return [_memoizedValue objectForKey:_updateContext.callID]; } - (void)addChild:(REANode *)child @@ -140,7 +148,7 @@ + (void)runPropUpdates:(REAUpdateContext *)context } [context.updatedNodes removeAllObjects]; - context.loopID++; + context.loopID = [[NSNumber alloc] initWithLong:context.loopID.longValue + 1]; } @end diff --git a/ios/Nodes/REAParamNode.h b/ios/Nodes/REAParamNode.h new file mode 100644 index 00000000000..af05d7336be --- /dev/null +++ b/ios/Nodes/REAParamNode.h @@ -0,0 +1,10 @@ +#import "REAValueNode.h" + +@interface REAParamNode : REAValueNode + +- (void)beginContext:(NSNumber*) ref + prevCallID:(NSNumber*) prevCallID; +-(void) endContext; + +@end + diff --git a/ios/Nodes/REAParamNode.m b/ios/Nodes/REAParamNode.m new file mode 100644 index 00000000000..b2f7b8b2596 --- /dev/null +++ b/ios/Nodes/REAParamNode.m @@ -0,0 +1,50 @@ +#import "REAParamNode.h" +#import "REAValueNode.h" +#import "REANodesManager.h" + +@implementation REAParamNode { + NSMutableArray *_argstack; + NSString *_prevCallID; +} + +- (instancetype)initWithID:(REANodeID)nodeID config:(NSDictionary *)config +{ + if ((self = [super initWithID:nodeID config:config])) { + _argstack = [NSMutableArray arrayWithCapacity:0]; + } + return self; +} + +- (void)setValue:(NSNumber *)value +{ + REANode *node = [self.nodesManager findNodeByID:[_argstack lastObject]]; + NSString *callID = self.updateContext.callID; + self.updateContext.callID = _prevCallID; + [(REAValueNode*)node setValue:value]; + self.updateContext.callID = callID; +} + +- (void)beginContext:(NSNumber*) ref + prevCallID:(NSString*) prevCallID +{ + _prevCallID = prevCallID; + [_argstack addObject:ref]; +} + +- (void)endContext +{ + [_argstack removeLastObject]; +} + + +- (id)evaluate +{ + NSString *callID = self.updateContext.callID; + self.updateContext.callID = _prevCallID; + REANode * node = [self.nodesManager findNodeByID:[_argstack lastObject]]; + id val = [node value]; + self.updateContext.callID = callID; + return val; +} + +@end diff --git a/ios/REANodesManager.m b/ios/REANodesManager.m index acc7ce0a57e..17323ec3865 100644 --- a/ios/REANodesManager.m +++ b/ios/REANodesManager.m @@ -19,7 +19,9 @@ #import "REAModule.h" #import "Nodes/REAAlwaysNode.h" #import "Nodes/REAConcatNode.h" -#import "REAModule.h" +#import "Nodes/REAParamNode.h" +#import "Nodes/REAFunctionNode.h" +#import "Nodes/REACallFuncNode.h" @interface RCTUIManager () @@ -231,6 +233,9 @@ - (void)createNode:(REANodeID)nodeID @"event": [REAEventNode class], @"always": [REAAlwaysNode class], @"concat": [REAConcatNode class], + @"param": [REAParamNode class], + @"func": [REAFunctionNode class], + @"callfunc": [REACallFuncNode class], // @"listener": nil, }; }); diff --git a/ios/RNReanimated.xcodeproj/project.pbxproj b/ios/RNReanimated.xcodeproj/project.pbxproj index 4669ded0c86..bfe2185bd8c 100644 --- a/ios/RNReanimated.xcodeproj/project.pbxproj +++ b/ios/RNReanimated.xcodeproj/project.pbxproj @@ -31,6 +31,12 @@ 44125DD12253A3C0003C1762 /* RCTConvert+REATransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DD02253A3C0003C1762 /* RCTConvert+REATransition.m */; }; 660A44292119B821006BFD5E /* REAConcatNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 660A44282119B820006BFD5E /* REAConcatNode.m */; }; 66240C6920C68DEA00648F55 /* REAAlwaysNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 66240C6820C68DEA00648F55 /* REAAlwaysNode.m */; }; + A12DA6B722EC228D00E8271A /* REAParamNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6B622EC228D00E8271A /* REAParamNode.m */; }; + A12DA6B822EC228D00E8271A /* REAParamNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6B622EC228D00E8271A /* REAParamNode.m */; }; + A12DA6BE22EC22E400E8271A /* REAFunctionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */; }; + A12DA6BF22EC22E400E8271A /* REAFunctionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */; }; + A12DA6C022EC22E400E8271A /* REACallFuncNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */; }; + A12DA6C122EC22E400E8271A /* REACallFuncNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */; }; FDBB1777229BF0DE00D1E455 /* REAModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2A209062F100EEC73A /* REAModule.m */; }; FDBB1778229BF0DE00D1E455 /* REANodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2B209062F100EEC73A /* REANodesManager.m */; }; FDBB1779229BF0E700D1E455 /* REATransitionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DBF22538E6D003C1762 /* REATransitionManager.m */; }; @@ -128,6 +134,12 @@ 660A442A2119B83E006BFD5E /* REAConcatNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAConcatNode.h; sourceTree = ""; }; 66240C6720C68DEA00648F55 /* REAAlwaysNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAAlwaysNode.h; sourceTree = ""; }; 66240C6820C68DEA00648F55 /* REAAlwaysNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAAlwaysNode.m; sourceTree = ""; }; + A12DA6B622EC228D00E8271A /* REAParamNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REAParamNode.m; sourceTree = ""; }; + A12DA6B922EC22A900E8271A /* REAParamNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REAParamNode.h; sourceTree = ""; }; + A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAFunctionNode.m; sourceTree = ""; }; + A12DA6BB22EC22E300E8271A /* REACallFuncNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REACallFuncNode.h; sourceTree = ""; }; + A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REACallFuncNode.m; sourceTree = ""; }; + A12DA6BD22EC22E300E8271A /* REAFunctionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAFunctionNode.h; sourceTree = ""; }; FDBB176E229BF04900D1E455 /* libRNReanimated-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNReanimated-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -160,6 +172,12 @@ 440FEC0C209062F100EEC73A /* Nodes */ = { isa = PBXGroup; children = ( + A12DA6B922EC22A900E8271A /* REAParamNode.h */, + A12DA6B622EC228D00E8271A /* REAParamNode.m */, + A12DA6BB22EC22E300E8271A /* REACallFuncNode.h */, + A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */, + A12DA6BD22EC22E300E8271A /* REAFunctionNode.h */, + A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */, 660A442A2119B83E006BFD5E /* REAConcatNode.h */, 660A44282119B820006BFD5E /* REAConcatNode.m */, 66240C6720C68DEA00648F55 /* REAAlwaysNode.h */, @@ -314,14 +332,17 @@ 440FEC3A209062F100EEC73A /* REAModule.m in Sources */, 44125DCE2253A038003C1762 /* REATransitionValues.m in Sources */, 44125DC82253906B003C1762 /* REAAllTransitions.m in Sources */, + A12DA6B722EC228D00E8271A /* REAParamNode.m in Sources */, 44125DC022538E6D003C1762 /* REATransitionManager.m in Sources */, 440FEC39209062F100EEC73A /* REABlockNode.m in Sources */, + A12DA6C022EC22E400E8271A /* REACallFuncNode.m in Sources */, 440FEC33209062F100EEC73A /* REABezierNode.m in Sources */, 44125DC322538F68003C1762 /* REATransition.m in Sources */, 440FEC2D209062F100EEC73A /* REAStyleNode.m in Sources */, 440FEC31209062F100EEC73A /* REACondNode.m in Sources */, 44125DD12253A3C0003C1762 /* RCTConvert+REATransition.m in Sources */, 440FEC2C209062F100EEC73A /* REAPropsNode.m in Sources */, + A12DA6BE22EC22E400E8271A /* REAFunctionNode.m in Sources */, 440FEC3B209062F100EEC73A /* REANodesManager.m in Sources */, 440FEC36209062F100EEC73A /* REAEventNode.m in Sources */, 44125DCB22539177003C1762 /* REATransitionAnimation.m in Sources */, @@ -345,14 +366,17 @@ FDE6D936229BF70F007F6716 /* REAValueNode.m in Sources */, FDE6D941229BF70F007F6716 /* REABezierNode.m in Sources */, FDBB177B229BF0E700D1E455 /* REAAllTransitions.m in Sources */, + A12DA6B822EC228D00E8271A /* REAParamNode.m in Sources */, FDE6D93D229BF70F007F6716 /* REASetNode.m in Sources */, FDE6D93A229BF70F007F6716 /* REABlockNode.m in Sources */, + A12DA6C122EC22E400E8271A /* REACallFuncNode.m in Sources */, FDE6D939229BF70F007F6716 /* REAPropsNode.m in Sources */, FDBB1778229BF0DE00D1E455 /* REANodesManager.m in Sources */, FDE6D937229BF70F007F6716 /* REAStyleNode.m in Sources */, FDE6D940229BF70F007F6716 /* REAJSCallNode.m in Sources */, FDE6D935229BF70F007F6716 /* REANode.m in Sources */, FDE6D93B229BF70F007F6716 /* REACondNode.m in Sources */, + A12DA6BF22EC22E400E8271A /* REAFunctionNode.m in Sources */, FDBB177C229BF0E700D1E455 /* REATransitionAnimation.m in Sources */, FDE6D934229BF70F007F6716 /* REAAlwaysNode.m in Sources */, FDE6D933229BF70F007F6716 /* REAConcatNode.m in Sources */, diff --git a/react-native-reanimated.d.ts b/react-native-reanimated.d.ts index 506a2a73949..ed5e642fca0 100644 --- a/react-native-reanimated.d.ts +++ b/react-native-reanimated.d.ts @@ -102,6 +102,7 @@ declare module 'react-native-reanimated' { finished: AnimatedValue; velocity: AnimatedValue; position: AnimatedValue; + prevPosition?: AnimatedValue; time: AnimatedValue; } export interface SpringConfig { @@ -217,6 +218,9 @@ declare module 'react-native-reanimated' { export const neq: BinaryOperator<0 | 1>; export const and: MultiOperator<0 | 1>; export const or: MultiOperator<0 | 1>; + export function proc( + cb: (...params: Array>) => Adaptable + ): (...args: Array>) => AnimatedNode; export function defined(value: Adaptable): AnimatedNode<0 | 1>; export function not(value: Adaptable): AnimatedNode<0 | 1>; export function set( diff --git a/src/animations/spring.js b/src/animations/spring.js index d27b345b48c..0b2ed444130 100644 --- a/src/animations/spring.js +++ b/src/animations/spring.js @@ -83,7 +83,9 @@ export default function spring(clock, state, config) { ); // conditions for stopping the spring animations - const prevPosition = new AnimatedValue(0); + const prevPosition = state.prevPosition + ? state.prevPosition + : new AnimatedValue(0); const isOvershooting = cond( and(config.overshootClamping, neq(config.stiffness, 0)), diff --git a/src/base.js b/src/base.js index 17e6bfe08c6..903adc3e97f 100644 --- a/src/base.js +++ b/src/base.js @@ -13,4 +13,7 @@ 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 { + createAnimatedFunction as proc, +} from './core/AnimatedFunction'; export * from './operators'; diff --git a/src/core/AnimatedCallFunc.js b/src/core/AnimatedCallFunc.js new file mode 100644 index 00000000000..f3fe2039349 --- /dev/null +++ b/src/core/AnimatedCallFunc.js @@ -0,0 +1,18 @@ +import AnimatedNode from './AnimatedNode'; +import { adapt } from './AnimatedBlock'; + +class AnimatedCallFunc extends AnimatedNode { + + constructor(proc, args, params) { + super({ + type: 'callfunc', + what: proc.__nodeID, + args: args.map(n => n.__nodeID), + params: params.map(n => n.__nodeID), + }, [...args]); + } +} + +export function createAnimatedCallFunc(proc, args, params) { + return new AnimatedCallFunc(proc, args.map(p => adapt(p)), params); +} diff --git a/src/core/AnimatedFunction.js b/src/core/AnimatedFunction.js new file mode 100644 index 00000000000..0b089f75fea --- /dev/null +++ b/src/core/AnimatedFunction.js @@ -0,0 +1,37 @@ +import AnimatedNode from './AnimatedNode'; +import { createAnimatedCallFunc } from './AnimatedCallFunc'; +import { createAnimatedParam } from './AnimatedParam'; + +class AnimatedFunction extends AnimatedNode { + constructor(what, ...params) { + super( + { + type: 'func', + what: what.__nodeID, + }, + [what, ...params] + ); + this.__attach(); + } +} + +export function createAnimatedFunction(cb) { + const params = new Array(cb.length); + for (let i = 0; i < params.length; i++) { + params[i] = createAnimatedParam(); + } + const what = cb(...params); + const func = new AnimatedFunction(what, ...params); + return (...args) => { + if (args.length !== params.length) { + throw new Error( + 'Parameter mismatch when calling reanimated function. Expected ' + + params.length + + ' parameters, got ' + + args.length + + '.' + ); + } + return createAnimatedCallFunc(func, args, params); + }; +} diff --git a/src/core/AnimatedParam.js b/src/core/AnimatedParam.js new file mode 100644 index 00000000000..ede22ef60f7 --- /dev/null +++ b/src/core/AnimatedParam.js @@ -0,0 +1,12 @@ +import AnimatedNode from './AnimatedNode'; + +class AnimatedParam extends AnimatedNode { + constructor() { + super({ type: 'param' }, []); + this.__attach(); + } +} + +export function createAnimatedParam() { + return new AnimatedParam(); +}