Open bdowning opened 8 years ago
I'm not wild about the shifty-fp
name because, as is hopefully apparent above, this is not intended to be a functional programming version of Shifty. Instead, it's the "core functionality" of Shifty, which just happens to be implemented in a functional-programming way.
These are some amazing ideas. Shifty is admittedly not the fastest tweening engine out there, but that's because I prioritized extensibility and flexibility over pure performance. Yours is a much more performance-driven approach, which can make a huge difference on some projects like the ones you are working on. I'm all for this.
The idea of compiling optimized interpolation functions is very interesting. I haven't seen this technique before and I wonder how it might stack up to GSAP, the current king of the hill in terms of performance for animation libraries. I estimate that this would be pretty close to GSAP's level.
Once this gets built out, I'd be totally open to replacing "classic" Shifty for this one as the interpolation engine for Rekapi. Doing so would probably necessitate a 2.0 release. I could also roll my ES6-ification work into that release, making for a pretty big advancement for Rekapi. Please let me know if you want to go that route, and I'll work with you to make it happen.
About the name: Yeah, shifty-fp isn't great. Not long ago, I started on a 2.0 version of Rekapi, which was a total rewrite, but I later decided to spin it off into a separate project called alt-rekapi. It's very incomplete, and I'm not working on it at the moment so as to focus on other projects. But, like this project, it's a reimagining of what Rekapi could be if built with modern tools and practices. It uses Redux and Immutable.js, so our heads are in the same place. Perhaps this could fit into the picture somehow? Since it's conceptually similar to this project, what do you think of "alt-shifty?"
Yeah, I hate names.
Thesaurus has "drift" as a synonym of "shift", and amazingly there's not "drifty" npm package yet, but there is a "drifty" company (https://github.com/driftyco) so that's no good. Besides, drift racing is kind of ridiculous. :D
Shift -> Mutate. Googling "mutaty" gets https://en.wiktionary.org/wiki/mutati. Calling a mostly-functional package anything like "mutate" is kind of ironic. ;)
Between -> Betwixt. Already an npm package.
"reshifty" sounds awkward.
I kind of like "retween". Actually the more I think about it the more I like it. Rekapi, Redux, Retween. I think I will go steal the npm package name if nothing else.
Well, I'm sold enough to rename the repository. ;-)
Retween is great! I love it.
Any particular reason why the easing formulas are exported as one big object instead of just exporting each function individually? My best guess would be compatibility with some other library, but it's jsut a guess.
Also, any particular reason why the exported functions in files where only one thing is exported aren't being export default
-ed?
Not criticizing, just interested in your coding style.
Hi @jv-PintoBobcat,
For 1), just because it was easy for the moment. They really should be separate so that a module builder can only take the ones that are being used.
For 2), mostly because I started with everything in index.js and haven't cleaned it up yet. I'm pretty much following Redux's coding style, so I will probably change it around to use default exports and then re-exporting with names in index.js like it does.
It's only 24 hours old after all! ;)
Fair enough. Code on, my good man.
@jeremyckahn, I did some looking at the speed test you linked above. It looks to me like the primary reason that GSAP is faster is not necessarily raw tweening performance, but rather that it is batching everything up into one requestAnimationFrame
call. Since Shifty Tweenable
s (as far as I know) don't know anything about each other, it's not doing this, and that really hurts performance.
I made a quick modification to Shifty to use a global requestAnimationFrame
and it's actually just as fast if not a bit faster than GSAP. The reason Shifty looks worse is because you're firing off each new dot in a setTimeout, and they start moving immediately; with 3000 dots multiples are bound to start at the same time, and so they "clump" in rings like you see. GSAP is using a delay parameter which enables more granularity (i.e. the dot can start moving "in-between" frames), so even if its frame rate slows down the starfield still looks correct.
Adding a similar delay parameter to Shifty should make it behave just the same as GSAP.
Incredible. Thanks for sharing your research! I definitely want to incorporate performance tweaks like this since they are so significant (and simple!). I'm a little backed up with various things right now, so I haven't had time to dig into your various changes and respond to all threads. It's high on my list of things to do, though – just letting you know that I haven't lost track of anything!
Turns out GSAP is still a bit faster. I'm pretty sure it has something to do with how they're batching and/or manipulating the DOM. At 3000 dots they get within 1-2 FPS of each other, but at 500 dots you can see in the profiler/timeline that GSAP has more idle time.
Batching seems like a smart idea. Even if there are multiple tweenable
instances, there's no reason that they shouldn't all be batched. I have opened a ticket to track this: jeremyckahn/shifty#87
Shifty actually does have delay functionality. I'm pretty sure I implemented that some time after I forked the GSAP performance test — I will have update my fork to use that to see what the performance difference is when I have some time. Thanks for the reminder!
@jeremyckahn wrote in jeremyckahn/rekapi#46:
The idea basically came from looking at what Rekapi uses Shifty for, which is basically just the interpolation engine. Other than reusing some simple Shifty methods on the Actors it doesn't appear that anything from it is required other than what gets called from
Tweenable.interpolate
. Specifically, all of the stuff for playing tweens (scheduling, timeoutHandler, persistent state handling, etc.) is not used. Unfortunately it looks like some of the efficiency hooks for the extensions like the token module depend on actually using a real, persistent Tweenable; some work is done ontweenCreated
which would only be run once on a real Tweenable but is repeated every time withTweenable.interpolate
because internally it constructs a new psuedo-Tweenable, uses it, then throws it away.I was having trouble figuring out how to modify the event-based hook system that the token module uses to be able to precalculate and save more of the work in a way amenable to being stored in a
Rekapi.KeyframeProperty
to improve performance in Rekapi. In the process of thinking about this I decided it made more sense to me to have just the interpolation functionality as a core, and then put an interface like the current Shifty on top of it to manage single tween playbacks and give it a more user-friendly UI, or put Rekapi on top for a more full-featured scene-based animation system.shifty-core
is my attempt to see what that might look like.The intent is that Rekapi could, at keyframe creation time, preprocess the keyframe value (saving the produced decoder function), and then create and save an interpolator. Then at playback time all that needs to happen (in
KeyframeProperty.getValueAt
) is something like:Notably, at playback time you did not have to parse either the current or next property's value (i.e. to tokenize and pick apart CSS), you directly call a function to interpolate the values, and you directly call a function to turn the tweened state back into the format needed for the Rekapi state (i.e. regenerate the CSS with the new values popped in).
Because you're directly calling an interpolation and decode function, these can be made to be very efficient. In fact, you can compile them!
Let's say you're tweening from
{transform: 'translateX(10px) rotate(10deg)'}
to{transform: translateX(25px) rotate(40deg)
}. Your token preprocessor will turn these into something like:It will also produce a
decoder
function to reverse the transformation for any state shaped like the above. Let's say you're easing with{transform: [ easeInOutSine, linear ]}
. The token preprocessor would have turned this into:Then you feed
toState
andtoEasing
intocreateInterpolator
, and get an interpolation function specific to thetoState
shape.These functions could be implemented as:
It looks wordy and ugly, but the resulting functions would be incredibly fast. Obviously you'll need to cache these so that you don't regenerate the same function over and over again, but that should be relatively easy. And again, all of this work can happen at keyframe creation time, not runtime.
I do want to get to the point of having a compiler like this, but first I'm just trying to get basic functionality and to prove out the API.
Basically, in summary: Extract the "interesting" parts out of Shifty (interpolation, preprocessing algorithms), ruthlessly minimize, and turn the efficiency up to 11.