飞镖/颤振:CustomPaint的更新速率低于ValueNotifier的值更新

时间:2020-07-12 08:59:23

标签: flutter dart ffi redraw custom-painting

我使用dart FFI从本机端检索数据,并用抖动CustomPaint显示数据。

我使用ValueNotifier控制CustomPaint的重绘。

代码:以一定速率轮询数据

使用状态类,我定期从本机端轮询数据,并将其分配给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。

然后将通知程序绑定到CustomPaint重绘

  @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,我用检索到的颜色绘制屏幕。

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");
  }

}

然后,我注意到从视觉上看颜色更新的速度低于轮询。尽管可以重涂,但可以通过同时查看我的日志记录和电话屏幕来观察到这一点。

问题

我应该如何实现感知到的同步更新?

我还应该补充一点,本地后端模拟以1秒的间隔在红色/绿色/蓝色之间切换颜色。

由于轮询的频率要高得多,因此我希望每隔1秒就能看到一个相当稳定的颜色变化。但是现在,颜色以更长的时间间隔改变。有时很少重涂,这可能需要几秒钟的时间,而轮询总是返回相当稳定的数据更新。

更新

根据我的测试,我应该保留setState,否则重新绘制只会停止。通过将数据更新切换到飞镖地,我发现一切都按预期进行。因此,它必须在本机端或FFI接口中。这是经过修改的dart代码,在不涉及FFI的情况下可以正常工作。

基本上,我使用恒定的颜色集合并对其进行迭代。


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;
      }

    });
  }

在本机方面,我有一个生产者/消费者线程模型。生产者以固定的速率循环浏览颜色集合。消费者只要受到生产者的检查就可以得到它。

#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()函数。因此,我假设此使用者函数在主线程中起作用。

所以我有一个想法,就是C ++端的线程协调可能会影响CustomPaint上的抖动渲染方式。

但是我不知道如何证明这一点。

1 个答案:

答案 0 :(得分:0)

我通过使用本机端的睡眠功能取得了一些进步。

这是我的发现:

  • 我过去通常使用1秒间隔从本地生产者线程中删除数据,并在本地端从主线程中使用它。消费者必须等待生产者ping。以这种速度,抖动渲染线程似乎已停止。
  • 通过将睡眠一直降低到20ms以下,渲染将按预期开始工作。
  • 即使睡眠时间为20毫秒,渲染中也会出现打h。

因此,我相信我的数据生成模型必须适应抖动,以确保轮询不会阻塞或以抖动的首选速率(例如60fps)附近的速率传递。