1, an overview of the
Animation and mini-games may seem to be two unrelated topics, but in fact they both rely on the creation of Vsync. For those who are not sure that animation relies on Vsync, check Out Gityuan’s blog for an in-depth understanding of Flutter animation [1]. The mini-game engine currently used in Flutter is Flame[2]. The GameLoop also uses Ticker(which relies on Vsync) to keep the Game refreshed. To implement animations and mini-games, we must provide Vsync on the Client side.
2. Vsync mechanism
Let’s start with a look at how Flutter is set up with Vsync. In our article on understanding the principles of Flutter animation [1], although the focus is on the animation flow, we have mentioned signing up for Vsync. For those of you who are more careful, Choreographer, Choreographer is just about picking up low-level Vsync signals to provide stable timing for upper-level apps to render, something Android students should be able to pick up soon. Let’s verify that Android side uses Choreographer in VsyncWaiter to provide Vsync timing for Flutter.
private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() { @Override public void asyncWaitForVsync(long cookie) { Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { float fps = windowManager.getDefaultDisplay().getRefreshRate(); Long refreshPeriodNanos = (long) (1000000000.0 / FPS); FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie); }}); }};Copy the code
While we are on the iOS side, we need to provide the Client side with the opportunity of Vsync. Let’s see how the Flutter is implemented on the iOS side. To the iOS implementation in flutter/shell/platform/Darwin/iOS/framework/Source/vsync_waiter_ios. Mm.
- (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner callback:(flutter::VsyncWaiter::Callback)callback { self = [super init]; if (self) { callback_ = std::move(callback); display_link_ = fml::scoped_nsobject<CADisplayLink> { [[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain] }; display_link_.get().paused = YES; task_runner->PostTask([client = [self retain]]() { [client->display_link_.get() addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [client release]; }); } return self; }... - (void)onDisplayLink:(CADisplayLink*)link { fml::TimePoint frame_start_time = fml::TimePoint::Now(); fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(link.duration); display_link_.get().paused = YES; callback_(frame_start_time, frame_target_time); }Copy the code
We can also use CADisplayLink to set up Vsync mechanism in Native Client side in the same way as the system.
3, Animation
The JS2Flutter framework is used by the Client side to drive the Host side rendering. Changes to the UI are basically made to the virtual tree on the Client side to drive changes to the real Widget tree on the Host side. Many students may think that you can constantly reconstruct the virtual tree during the animation interpolation process to achieve animation, but in fact, this method is inefficient and unnecessary. Animation only affects the boundary deformation of the Widget tree (matrix transformation), and does not change the structure of the Widget tree. So we can only animate the real Widget on the Host side, and the Client side will keep the value and state of the animation updated in real time to ensure logical correctness.
To animate a real Widget tree, you have to build a real Animation, AnimationController on the Host side, and a pure Api proxy on the Client side. We just need to match the Animation and AnimationController on the Client side with the Host side.
The AnimationController construction also relies on the TickerProvider. When the AnimationController is created on the Client side, we also need to create the real body on the Host side. Where does the real body depend on the TickerProvider? Remember our implementation of AppLifecycleState in the right-most JS2Flutter framework — rendering mechanism [3]? AppContainer can be used to provide a reliable TickerProvider, since its lifetime is equal to the lifetime of the whole Flutter App. Another problem is to ensure the accuracy of the value and state of the Animation on the Client side. By virtue of the bidirectional synchronous communication mechanism described in the communication mechanism of THE JS2Flutter framework [4] in the last article, we can monitor the changes of the real Animation. In this way, the Animation on the Client side is modified synchronously.
Many business scenarios need to monitor the Animation update to make CHANGES on the UI. In such scenarios, it is inevitable to rebuild the virtual tree, so we try to update the Widget tree with smaller granularity. For example, if we want to implement a card flip animation, in the middle of the animation, we need to show the back, in this case we only do the card content update.
Widget build(BuildContext context) { final front = widget.childFront; final back = widget.childBack; Matrix4 transform = Matrix4.identity().. rotateY(_animation.value); return AnimatedBuilder( animation: _animation, builder: (BuildContext context, Widget child) { return Transform( transform: transform, alignment: Alignment.center, child: IndexedStack(alignment: align.center, children: <Widget>[front, back,], index: _animationCtr. Value < 0.5? 0:1,),); }); }Copy the code
4. Mini-games
Flame[2] is the most recent mini game engine to be used. To implement the mini game capability, we must first understand the implementation of Flame, especially how it is drawn. Here is the conclusion.
class GameRenderBox extends RenderBox with WidgetsBindingObserver { BuildContext context; Game game; GameLoop gameLoop; GameRenderBox(this.context, this.game) { gameLoop = GameLoop(gameLoopCallback); }... void gameLoopCallback(double dt) { if (! attached) { return; } game.recordDt(dt); game.update(dt); markNeedsPaint(); } @override void paint(PaintingContext context, Offset offset) { context.canvas.save(); context.canvas.translate( game.builder.offset.dx + offset.dx, game.builder.offset.dy + offset.dy); game.render(context.canvas); context.canvas.restore(); }... }Copy the code
The gameLoopCallback will be called every time Vsync is performed, and the refresh will be marked every time. The Component of the Game will be drawn on the Canvas, and the Component will determine its position and the content to be drawn. The rendering of small games is drawn on the Canvas. So we need to support Canvas capability first.
Let’s first look at how Flutter implements Canvas. We use the rotate as an example:
void rotate(double radians) native 'Canvas_rotate';
Copy the code
Framework layer provides Canvas, finally actually increased Engine layer flutter/lib/UI/painting/Canvas. The cc function of the same name, and then call SkCanvas function of the same name. We also adopt the same strategy. The Client side declares the mirror Canvas, providing the ability to equal the Flutter Canvas. The Client side mirror Canvas function calls are directly converted to the Flutter Canvas function calls through the communication channel. In order to achieve efficient drawing of Canvas, StandardMessageCodec is adopted to digitize Canvas instruction.
So we just need to implement Flame. When Native notifies Client Vsync, collect the instructions drawn on Canvas, digitize them to Host via StandardMessageCodec, and parse them out. Restore these command operations and draw the Game preoccupying the Host side to the Canvas.
5. Conclusion
This paper mainly describes the establishment of JS2Flutter framework Vsync mechanism, as well as the implementation of Animation and small games. Based on the previous several articles, we believe that you have more understanding of the JS2Flutter framework, hoping to inspire and help you. We will continue to explore the path of Flutter dynamics. Welcome to follow.
6. References
[1]: In-depth understanding of Flutter animation gityuan.com/2019/07/13/…
[2] : Flame github.com/flame-engin…
[3]: right-most JS2Flutter framework – rendering mechanism xie.infoq.cn/article/5c2…
[4]: Rightmost JS2Flutter framework – Communication mechanism xie.infoq.cn/article/f23…