dart / flutter: CustomPaint обновляется с меньшей скоростью, чем обновление значения ValueNotifier - PullRequest
0 голосов
/ 12 июля 2020

Я использую 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}');
    });
  }

  ....

}

Обратите внимание, что я опрашиваю со скоростью около 60 кадров в секунду.

И я привязываю уведомитель к перерисовке 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)
          )
        )
      )
    );
  }

Код: Repaint 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");
  }

}

Затем я заметил, что визуально обновления цвета происходят с меньшей скоростью, чем при опросе. Это наблюдается при одновременном просмотре моего журнала и экрана телефона, хотя перекраска работает.

Вопрос

Как мне добиться воспринимаемых одновременных обновлений?

I Следует также добавить, что встроенная имитация серверной части переключает цвета между красным / зеленым / синим с интервалом в 1 секунду.

Поскольку опрос выполняется гораздо чаще, я ожидаю увидеть довольно стабильное изменение цвета примерно на 1- второй интервал. Но сейчас цвета меняются с большим интервалом. Иногда перерисовка вызывается очень редко, что может занять несколько секунд, при этом опрос возвращает довольно стабильные обновления данных.

Обновление

Согласно моему тесту, я должен оставить setState, иначе перерисовка просто останавливается. Также переключив обновление данных на dart land, я обнаружил, что все работает, как ожидалось. Так что это должно быть что-то на нативной стороне или в интерфейсе FFI. Вот модифицированный код дротика, который работает должным образом, когда 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 голосов
/ 12 июля 2020

Я добился некоторого прогресса, поиграв с функцией сна на нативной стороне.

Вот мои выводы:

  • Раньше я использовал 1-se c interval для извлечения данных из собственного потока-производителя и потребления их из основного потока также на собственной стороне. Потребитель должен дождаться пинга производителя. При такой скорости поток рендеринга флаттера, кажется, остановлен.
  • При уменьшении времени ожидания до менее 20 мс рендеринг начинает работать должным образом.
  • Даже при 20 мс сна есть икота при рендеринге.

Итак, я считаю, что моя модель генерации данных должна адаптироваться к флаттеру, чтобы гарантировать, что опрос не должен блокироваться или доставляться со скоростью около предпочтительной скорости флаттера, скажем, 60 кадров в секунду.

...