0

我使用 dart FFI 从本机端检索数据,并使用 flutter 显示数据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 秒的间隔看到相当稳定的颜色变化。但是现在颜色的变化间隔更长。有时 repaint 很少被调用,可能是几秒钟,而轮询始终返回相当稳定的数据更新。

更新

根据我的测试,我应该保留setState,否则重绘只会停止。此外,通过将数据更新切换到飞镖地,我发现一切都按预期工作。所以它必须是本机端或 FFI 接口中的东西。这是在不涉及 FFI 时按预期工作的修改后的 dart 代码。

基本上我使用一个恒定的颜色集合并遍历它。


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

    });
  }

在本机方面,我有一个生产者/消费者线程模型正在工作。生产者以固定速率循环遍历颜色集合。每当生产者 ping 消费者时,消费者就会得到它。

#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.

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

4

1 回答 1

0

通过在本机端玩弄睡眠功能,我取得了一些进展。

以下是我的发现:

  • 我曾经使用 1 秒的间隔从本地生产者线程中生成数据,并在本地端的主线程中使用它。消费者必须等待生产者 ping。以这种速度,颤振渲染线程似乎处于停止状态。
  • 通过将睡眠时间一直减少到 20 毫秒以下,渲染开始按预期工作。
  • 即使是 20 毫秒的睡眠,渲染也会出现问题。

所以我相信我的数据生成模型必须适应颤振,以确保轮询不应该阻塞或应该以颤振首选速率附近的速率传递,比如 60fps。

于 2020-07-12T10:35:46.940 回答