问题描述
我使用dart FFI从本机端检索数据,并用颤抖的 CustomPaint
显示数据。
I use dart FFI to retrieve data from native side, and show the data with flutter CustomPaint
.
我使用 ValueNotifier
来控制 CustomPaint
重绘。
使用状态类,我会定期从本机端轮询数据,并将其分配给 ValueNotifier
。
With a state class, I poll data from native side periodically, and assign it to the ValueNotifier
.
class _ColorViewState extends State<ColorView> {
ValueNotifier<NativeColor> _notifier;
Timer _pollTimer;
@override
void initState() {
// TODO: implement initState
super.initState();
ffiInit();
// initialize notifier
_notifier = ValueNotifier<NativeColor>(ffiGetColor().ref);
_pollTimer = Timer.periodic(Duration(milliseconds: 16), _pollColor);
}
_pollColor(Timer t) {
setState(() {
print('polling ...');
_notifier.value = ffiGetColor().ref;
print('polled: ${_notifier.value.r}, ${_notifier.value.g}, ${_notifier.value.b}');
});
}
....
}
请注意,我以大约60fps的速率进行轮询。
Note that I poll at the rate of about 60fps.
并将通知者绑定到CustomPaint重绘
And I bind the notifier to the CustomPaint repaint
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
width: double.infinity,
height: double.infinity,
color: widget.clrBackground,
child: ClipRect(
child: CustomPaint(
painter: _ColorViewPainter(
context: context,
notifier: _notifier,
clrBackground: Color.fromARGB(255, 255, 0, 255)
)
)
)
);
}
代码:反应性地重绘CustomPaint
然后用CustomPaint的重新绑定到 ValueNotifier
,我用检索到的颜色绘制屏幕。
Code: Repaint CustomPaint reactively
Then with the CustomPaint's repaint bound to the ValueNotifier
, I paint the screen with the retrieved colour.
class _ColorViewPainter extends CustomPainter {
ValueNotifier<NativeColor> notifier;
BuildContext context;
Color clrBackground;
_ColorViewPainter({this.context, this.notifier, this.clrBackground})
: super(repaint: notifier) {
}
@override
bool shouldRepaint(_ColorViewPainter old) {
print('should repaint');
return true;
}
@override
void paint(Canvas canvas, Size size) {
print("paint: start");
final r = notifier.value.r;
final g = notifier.value.g;
final b = notifier.value.b;
print("color: $r, $g, $b");
final paint = Paint()
..strokeJoin = StrokeJoin.round
..strokeWidth = 1.0
..color = Color.fromARGB(255, r, g, b)
..style = PaintingStyle.fill;
final width = size.width;
final height = size.height;
final content = Offset(0.0, 0.0) & Size(width, height);
canvas.drawRect(content, paint);
print("paint: end");
}
}
然后我注意到视觉上的颜色更新是在低于投票率。尽管重涂有效,但可以同时查看我的日志记录和电话屏幕来观察到这一点。
Then I noticed that visually the colour updates are at a lower rate than the polling. This is observed by looking at my logging and the phone screen at the same time, although the repaint works.
我应该如何实现感知同时更新?
How should I achieve perceived simultaneous updates?
我还应该补充一点,本地后端模拟以1秒的间隔在红色/绿色/蓝色之间切换颜色。
I should also add that the native backend simulation switches colours between red/green/blue at an 1-second interval.
自轮询的频率要高得多,我希望每隔1秒钟就能看到一个相当稳定的颜色变化。但是现在,颜色以更长的时间间隔改变。有时很少重涂,这可能需要几秒钟,而轮询总是返回相当稳定的数据更新。
Since the polling is much more frequent, I expect to see a fairly stable colour changing at about 1-second interval. But right now the colours change at a longer interval. Sometimes repaint is called very rarely, which could be several seconds, all the while the polling returns quite stable data updates.
根据我的测试我应该保持 setState
,否则重新绘制就停止了。另外,通过将数据更新切换到飞镖土地,我发现一切都按预期进行。因此,它必须在本机端或FFI接口中。这是经过修改的dart代码,当不涉及FFI时,它可以按预期工作。
According to my test I should keep setState
otherwise the repaint simply stops. Also by switching the data update to dart land I found that everything works as expected. So it must be something on the native side or in the FFI interface. Here is the modified dart code that works as expected when no FFI is involved.
基本上,我使用常量颜色集合并对其进行迭代。
Basically I use a constant colour collection and iterate through it.
class _ColorViewState extends State<ColorView> {
ValueNotifier<NativeColor> _notifier;
Timer _pollTimer;
var _colors;
int _step = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
ffiInit();
// constant colour collection
_colors = [
[255, 0, 0],
[0, 255, 0],
[0, 0, 255]
];
_notifier = ValueNotifier<NativeColor>(ffiGetColor().ref);
_pollTimer = Timer.periodic(Duration(milliseconds: 1000), _pollColor);
}
_pollColor(Timer t) {
setState(() {
print('polling ...');
// _notifier.value = ffiGetColor().ref;
_notifier.value.r = _colors[_step][0];
_notifier.value.g = _colors[_step][1];
_notifier.value.b = _colors[_step][2];
print('polled: ${_notifier.value.r}, ${_notifier.value.g}, ${_notifier.value.b}');
if (++_step >= _colors.length) {
_step = 0;
}
});
}
在本机方面,我使用了生产者/消费者线程模型。生产者以固定的速率循环浏览颜色集合。
On the native side, I have a producer/consumer thread model working. The producer is looping through the colour collection at a fixed rate. And the consumer gets it whenever pinged by the producer.
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#ifdef __cplusplus
#define EXTERNC extern "C" __attribute__((visibility("default"))) __attribute__((used))
#else
#define EXTERNC
#endif // #ifdef __cplusplus
struct NativeColor {
int r;
int g;
int b;
};
NativeColor* gpColor = nullptr;
NativeColor gWorker = {255, 0, 255};
// producer / consumer thread tools
std::thread gThread;
std::mutex gMutex;
std::condition_variable gConVar;
int gColors[][3] = {
{255, 0, 0},
{0, 255, 0},
{0, 0, 255}
};
int gCounter = 0;
int gCounterPrev = 0;
EXTERNC void ffiinit() {
if(!gpColor) {
gpColor = (struct NativeColor*)malloc(sizeof(struct NativeColor));
}
if(!gThread.joinable()) {
gThread = std::thread([&]() {
while(true) {
std::this_thread::sleep_for (std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(gMutex);
gWorker.r = gColors[gCounter][0];
gWorker.g = gColors[gCounter][1];
gWorker.b = gColors[gCounter][2];
if(++gCounter == 3) {
gCounter = 0;
gCounterPrev = gCounter;
}
lock.unlock();
gConVar.notify_one();
}
});
}
}
EXTERNC struct NativeColor* ffiproduce() {
// get yellow
gpColor->r = 255;
gpColor->g = 255;
gpColor->b = 255;
std::unique_lock<std::mutex> lock(gMutex);
gConVar.wait(lock, [&]{
return gCounter > gCounterPrev;
//return true;
});
*gpColor = gWorker;
gCounterPrev = gCounter;
lock.unlock();
return gpColor;
}
ffiproduce()
绑定到飞镖端的 ffiGetColor()
函数。因此,我假定此使用者函数在主线程中起作用。
ffiproduce()
is bound to the dart-side ffiGetColor()
function. So I assume this consumer function works in the main thread.
所以我有一个想法,就是C ++端的线程协调可能会影响通过 CustomPaint进行颤振渲染的方式。
。
So one idea I have is that maybe the thread coordination on C++ side affected the way flutter renders through CustomPaint
.
但是我不知道如何证明这一点。
But I have no idea how to prove that at this point.
推荐答案
我通过使用本机端的睡眠功能取得了一些进步。
I made some progress by playing around with the sleep function on the native side.
这是我的发现:
- 我过去通常使用1秒间隔从本地生产者线程中搅出数据,并在本地端从主线程中使用它。消费者必须等待生产者ping。以这种速度,抖动渲染线程似乎停止了。
- 通过将睡眠一直降低到20ms以下,渲染开始按预期方式工作。
- 即使睡眠时间为20毫秒,渲染中也会出现打h。
因此,我相信我的数据生成模型必须适应抖动,以确保轮询不应阻塞或以扑动的首选速率(例如60fps)附近的速率传送。
So I believe my data generation model must adapt to flutter to ensure that the polling should not block or should deliver at the rate around flutter's preferred rate, say, 60fps.
这篇关于dart / flutter:CustomPaint更新的速度低于ValueNotifier的值更新的速度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!