2022年7月30日 星期六

用 dart 語法將 function/task 的迴圈改成遞迴函式運作

當一個  function/task 用迴圈(while/for/do-while)處理狀態機(state machine)時, 如下所述:
import 'dart:async';
int step = 0;
void functionTask( )  async {
    for (int i = 0; i < loopMax; i++) {
        switch(step) {
            case 0: // ...
                    step ++;
                    break;
            case 1: //...
                    step ++;
                    break;
            // ...
            // case n: step ++;
            //      break ;
            default: step = 0;
                    break;
        }
       await  Future.delayed(Duration(microseconds: 0)); // schedule, to run ASAP
       if (step == 0) break;
    }
    return;
}

這樣的寫法會造成程序(thread)堵(blocking)在迴圈內, 如果用在 Flutter 平台(Platform)上, 將沒機會處理內部訊息(message queue), 導致 Widget 無法同步更新, 或者看不到畫面, 此外 StatefulWidget  管理狀態(state)時,  在 initState( ) 或是 build( ) 的 function/task 內是不能使用 async/await 的,  但可以註冊類同步(then)方式來運作,  搭配遞迴函式編寫方式移除迴圈, 同上述  function/task 的狀態機處理函式就能活動不停, 同步更新畫面, 直到結束:
import 'dart:async';
int step = 0;
Future<int>   async_Task( )  async { // 非同步涵式    
        switch(step) {// 現在函式
            case 0: // ...
                    step ++;
                    break;
            case 1: //...
                    step ++;
                    break;
            // ...
            // case n: step ++;
            //      break ;
            default: step = 0;
                    break
        }  
        await Future.delayed(Duration(microseconds: 1000000)); // return the 'Future'.
        // 未來函式 ...
        return step ;// 未來函數, 接觸點
}

        // ... in  stateManagement  class  ...
        void recursiveLoop( ) {// 類同步註冊: 接觸過往, 持續未來
           
async_Task( ) .then((result) {
                if (result > 0) setState ( ( ) =>
recursiveLoop( ) );// 接序函式: 建新 widget, 更新畫面
            });
        }

       
        @override  void  initState( ) {
            super .initState( );
            recursiveLoop( );
        }

2022年7月27日 星期三

dart Iterator 的運作方法

 為了讓類型可以運用在 for (  in )  { } 迴圈上, 需要繼承 IterableBase 並實現 Iterator 介面類型, 總共有 3 個方法需要實現, 詳見以下範例程式 main.dart:

import 'dart:collection';
import 'dart:io';
class  Element {
  final id;
  static int _sn = 0;
  Element(): id = ++ _sn;
}

class ABC extends IterableBase<Element> implements Iterator<Element> {
  final List<Element> array;
  int lastone = 0;// length snapshot
  int index   = 0;// start snapshot
  @override Element get current => array[index];//get 方法 current 實現
  @override bool moveNext( ) => index != lastone && (++ index >= 0);//方法 moveNext( )實現
  @override Iterator<Element> get iterator {//get 方法 iterator 實現
    lastone = array.length - 1;
    index  = -1;
    return this;
  }
  ABC([List<Element>? mjList]): array = mjList ?? [ ] {
    for(int i = 0; i< 10; i++) array.add(Element( ));
  }
}

void main( ) {
  final a = ABC();
  for (final e in a) {
    stdout.write("[${a.index}] = ${e.id}, ");
  };
  print(":: index = ${a.index} lastone= ${a.lastone}\n");

  for (final e in a) {
    stdout.write("[${a.index}] = ${e.id}, ");
  };
  print(":: index = ${a.index} lastone= ${a.lastone}\n");
}

執行 dart main.dart 看結果:

[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = 6, [6] = 7, [7] = 8, [8] = 9, [9] = 10, :: index = 9 lastone= 9

[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = 6, [6] = 7, [7] = 8, [8] = 9, [9] = 10, :: index = 9 lastone= 9

2022年7月20日 星期三

複習 Dart 語法

dart 語法跟 c++ 非常類似, 習慣 c++ 語言應該很容易進入狀況, 參考官方文件: https://dart.dev/guides/language/language-tour


1. 常用類型: bool, int, double, String, List, Set, Map, BigInt
    BigInt: 大整數, 無限整數類型
    List 陣列, 用 [value, ... ] 可用中括號表列陣列值, 有序, 從 0 開始
    Set  集合, 用 {value, ... } 可用大括號列舉集合值, 無序
    Map  字典, 用 {key:value, ...} 可用大括號列舉成對 key:value, HashMap 無序, LinkedHashMap 依插入順序為序, SplayTreeMap 則排序過, 若無特別指定 Map 則用 LinkedHashMap 來實現. Map 以 [key] 當索引鍵, 存取內容時, 操作起來就像是陣列.
    整數 (char, int, int64, long, long long) 統一用 int, 浮點數(float, double) 統一用 double
    String: 可用成對單引號 '這是字串' 或 成對雙引號 "This 'is' string", 單雙引號混用時要注意成對的先後次序, 不得有穿插的情形
    Note:
        Sting: 使用 String.fromCharCodes([ ]) 將整數陣列轉為字串, 使用 .codeUnitAt(0) 將字元轉為整數, dart 沒有 char 類型, 字元可使用 int 或 String 來處理, 例如:
        int c = 'c'.codeUnitAt(0);
        String str = String.fromCharCodes([97]);
        字串串接, 在引號內使用 ${ } 當作串接位置(place holder), 例如: '... ${變數 或 運算式} ...'
    宣告關鍵字:
        const   固定值, 類型, 物件內容, 成員都不能改變
        final   最終類型的物件, 類型物件不能再變, 但物件裡的成員可改變
        var  固定的類型, 類型無法再變, 可重新指定為同類型的物件
        dynamic 變動的類型(類型, 物件, 內容全都能重新指定)
        
2. 類型無須使用 new, 直接呼叫新類型建構式就能實例化物件, 可使用 is 來檢驗物件類型, dart 不用指標, 只有物件, 使用 . 取用物件的成員

3. 基本運算
    四則運算: +, -, *, /, %, ~/, ++, --
    邏輯運算: !, &&, ||
    比較運算: >, <. >=, <=, ==, !=
    位元運算: ~, &, |, ^, >>, <<
    條件運算: 條件式 ? 正值 : 負值;
    
4. 標準輸出/輸入函式:
    ptint(""); // 自動輸出換行
    stdout.write(""); // 輸出無換行,  須 import "dart:io"
    stdin.readLineSync();// 輸入,  須 import "dart:io"
    
5. 引入程式庫 import, 引出程式庫 export, 專案底下目錄 lib 是專案程式庫的起始點(project library root)

6.透過 pubspec.yaml 指引, 程式庫相關性用命令 pub get 來管理

7. static 只能用在 class { }內, 不能用於 function( ){ } 內, c 的 static 實際上也是放在 global 區, 用符號連結過去而已, 若有必要, 可將變數宣告在 global 區在 function(){ } 內共享.

8. 函式內的預設參數(default parameter), 可以用中括號 [val, ...] 宣告預設位置參數, 或是大括號{key:value, ...} 宣告預設帶名參數, 兩者僅能擇其一, 不能同時使用. 預設參數宣告時, 必須放在無預設值後面, 但呼叫時, 除了位置參數需遵循宣告位置的順序外, 帶名參數可以放在任何位置, SDK 2.17 版之後, 不需擺在位置參數之後. 帶名參數前若使用 required 修飾, 呼叫時就必須給定該帶名參數的數值

9. 透過 FFI 可以用 C 語言直接與作業系統相互溝通

10.  理解 async/await 與 Isolate.spawn( ) 的運作邏輯:

await 一個 Future<T>(未來物件)必須放在 async 區塊內, 只要將函式區塊宣告成 async 變成非同步函式, 就可以用來 await 未來物件, 同時也可以在其它 async (非同步)區塊內被 await. 呼叫 async 函式, 意味著當執行到 await 時, 後續程序會安排到排程運行. 後續可以註冊(then)一個回調函式(callback function), 一旦在排程內執行完畢, 除了呼叫此回調函式外, 同時將結果一起傳回來. 因此呼叫 async 函式並不會阻塞程式的運行, 而是將非同步函式硬是拆成兩段(以 await 為分界點, 若無 await 就一直執行程式到最後), 前段執行完, 返回一個未來物件, 繼續往下運行, 後段將在排程中運行, 簡單來說就是非同步程序拆成現在與未來(先後)執行順序

子代孵化 Isolate.spawn(void Function(SendPort), SendPort) 則是強迫程序分裂成兩條路徑並行(另類的 multithread, 但相互獨立的執行空間), 新孵化的子代 isolate 運作後便一去不返, 父子代 isolate 只能透過 sendPort 與 receivePort 單向交流(sendPort 負責發送訊息, receivePort 用於聆聽訊息), 若父子代要雙向傳輸, 必須在子代程序內另外產生一個 receivePort, 把成員 sendPort 經由 Isolate.spawn 所附帶的 sendPort 傳給父代, 父代透過所收到的 sendPort 就能向子代發出訊息, 雙向溝通管道才建立完成. 父子代都是名副其實的隔離物件, 無法透過全域或區域變數共享任何資源, 測試程式:

import "dart:async";
import "dart:isolate";
void main( ) {
  print("Main thread isoloate: ${Isolate.current.debugName.toString()}");
 
  final bgIsolate = (SendPort port) {
        print("${DateTime.now()}:new  ${Isolate.current.debugName.toString()}");       
        Isolate.exit(port, "Finish");
  };
 
  final port = ReceivePort( );
  final child = Isolate.spawn(bgIsolate, port.sendPort, debugName: "childIsolate");

  child.whenComplete(() {//注意:並不是指 bgIsolate 程序執行完成,而是當 isoloate 孵化完成!
    print("${DateTime.now()}: spawn complete.");
  });

  port.first.then((value){
        print("${DateTime.now()}: childIsolate return $value");
        port.close( );
  });
 
  Timer(Duration(milliseconds: 3000), ( ) {
    print("${DateTime.now()}: time is up.");
  });
}
開啟終端機執行  dart   main.dart  看結果:
Main thread isoloate: main
2022-07-23 16:32:51.726479:new  childIsolate
2022-07-23 16:32:51.747431: spawn complete.
2022-07-23 16:32:51.753271: childIsolate return Finish
2022-07-23 16:32:54.740046: time is up

11.  dart 不需 compile 直接用 dart VM 執行:  dart  main.dart ,  或是用  dart compile exe main.dart 翻譯成原生系統上可執行的獨立執行檔 main.exe

12. 透過 dart compile 事先編譯, 將 .dart 原始碼轉換成不同平台上可執行的的代碼(exe/aot-snapshot/jit-snapshot/kernel/js)
    a. dart compile js main.dart -o main.js 用來將 dart 語法轉換成 javascript 語法, 用 nodejs main.js 就能執行
    b. dart VM 內含 dart runtime 可執行 kernel snapshot 及 jit-snapshot 代碼.
    c. kernel snapshot 是與硬體系統(x86, arm, powerPC, mips)無關的 dart VM 代碼(dart byte code), dart VM 仍要對他二次解譯(compile), 轉成機械碼(machine code), 才能執行.
    d. 所轉成的 jit-snappsot 代碼可以讓 dart VM 加速翻譯成 machin code 並執行, 因此作業系統上仍必需有相對應的 dart VM, 用來分配記憶體/解譯成 machine code/執行.
    e. aot-snapsot 是作業系統上(windows, ios, linux)可執行的機械碼(machine code), 無需再解譯(compile), 透過 dartaotruntime 執行該 aot-snapshot, 不用透過 dart VM, 就能呼叫作業系統的 runtime 來執行
    f. exe 執行檔是把 aot-snapshot 及 dart runtime 全包在一起, 在作業系統上用 dart compile exe main.dart -o main 轉成獨立執行檔 main, 在 shell 底下 ./main 就能執行
    g. 理論上執行速度: kernel  < jit-snapshot < aot-snapshot < exe

2022年7月17日 星期日

Linux 系統使用 termios2 客製化 baud rate

簡單的 class,用來與 uBit V1.5 的 Serial port (/dev/ttyACM0)做溝通, 原始程式碼:

 #ifndef MySerial_H
#define MySerial_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define defaultBaudrate 115200

class MySerial {
  private:
    int fd = 0;
  public:
    ~MySerial() { if (fd) close(fd);  }
    MySerial(const char *dev = "/dev/ttyACM0", int baud = defaultBaudrate) {
      int ttyFD = open(dev, O_RDWR | O_NONBLOCK);
      if (ttyFD > 0) {
        fd = ttyFD;
        struct termios2 uart2;
        uart2.c_iflag     = 0;
        uart2.c_oflag     = 0;
        uart2.c_lflag     = 0;
        uart2.c_cc[VMIN]  = 1;
        uart2.c_cc[VTIME] = 0;
        uart2.c_cflag     = CS8 | CREAD | CLOCAL;
        uart2.c_cflag    &= ~CBAUD;
        uart2.c_cflag    |= CBAUDEX;
        uart2.c_ispeed = baud;
        uart2.c_ospeed = baud;
        ioctl(fd, TCSETS2, &uart2);       
      }
    }
    bool ready() { return fd > 0;  }

    int available() {
      if (fd > 0) {
          int bytes;
          ioctl(fd, FIONREAD, &bytes);
          return bytes;
      }
      return 0;
    }

    ssize_t write(char *buffer) {
      if (fd && buffer) return ::write(fd, buffer, strlen(buffer));// use global write      
      return 0;
    }

    ssize_t read(char *buffer, int n) {
      if (fd && buffer && n) return ::read(fd, buffer, n); // use global read
      return 0;
    }

    int printf(const char *fmt, ...) {
      static char strBuffer[1024];// Note: upto 1024 characters
      va_list parameters;
      va_start(parameters, fmt);
      vsprintf(strBuffer, fmt, parameters);
      va_end(parameters);
      return write(strBuffer);// this->write
    }
};
#endif

簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...