Microsoft 純正 Dependency Injection ライブラリ試してみた

V-VMDependency Injection でつなげる例

using System;
using System.Windows;
using MessagePipe;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace MessagePipeTest2
{
    public partial class App : Application
    {
        IHost host;

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            host = CreateHostBuilder().Build();

            var vm = host.Services.GetRequiredService<MainWindowVM>();
            var w = new MainWindow();

            w.DataContext = vm;
            w.Closed += (sender, e) => host.Dispose();
            w.Show();

            host.RunAsync();
        }

        static IHostBuilder CreateHostBuilder()
        {
            return Host.CreateDefaultBuilder()
                .ConfigureServices((ctx, services) =>
                {
                    services.AddMessagePipe();
                    services.AddHostedService<Worker>();
                    services.AddSingleton<MainWindowVM>();
                });
        }
    }
}

AddSingleton で VM をサービスに登録。必要なタイミングで GetRequiredService でインスタンス化して V の DataContext に設定。

M の部分は AddHostedService でサービスに登録すると同時にインスタンス化している。

VM - M 感は MessagePipe の Pub-Sub で繋げてやり取りする。

↓↓↓↓↓↓↓ VM

using System;
using MessagePipe;
using Prism.Mvvm;

namespace MessagePipeTest2
{
    class MainWindowVM : BindableBase
    {
        private int _value;
        public int Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        ISubscriber<MyEvent> subscriber;
        readonly IDisposable disposable;

        public MainWindowVM(ISubscriber<MyEvent> subscriber)
        {
            this.subscriber = subscriber;

            var bag = DisposableBag.CreateBuilder();

            this.subscriber.Subscribe(Callback).AddTo(bag);

            disposable = bag.Build();
        }

        public void Callback(MyEvent e)
        {
            Value = e.Value;
            Text = e.Text;
        }

        void Close()
        {
            disposable.Dispose();
        }
    }
}

↓↓↓↓↓↓↓↓ M

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MessagePipe;

namespace MessagePipeTest2
{
    class Worker : BackgroundService
    {
        IPublisher<MyEvent> publisher;

        MyEvent e;

        public Worker(IPublisher<MyEvent> publisher)
        {
            this.publisher = publisher;

            e = new MyEvent();
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() =>
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    e.Value++;
                    e.Text += DateTime.Now.ToString() + "\n";
                    publisher.Publish(e);
                }
            });
        }
    }
}

CySharp/MagicOnion を試してみた

CySharp/MagicOnion は以下のリポジトリにあります

github.com

基本的にはREADMEを読むか、リポジトリ直下にある sample を参考にすれば動作はわかると思います。

以下の記事は本家の人のブログ

tech.cygames.co.jp

試した結果はGitHubに置きました。

github.com

  • GrpcService2 はサーバーサイド
  • ConsoleApp1 はコンソールベースのクライアント
  • WpfApp1WPFベースのクライアント
  • ClassLibrary1 はインターフェースを定義しているライブラリ。サーバーとクライアントで共有している

感想

gRPCの使用感で簡単に使えるのはいいかなと思いました。 MessagePack を使用するので通信に乗せるクラスはすべて定義が必要なのがちょっとめんどくさい感じがありますが、いったん定義してしまえばだれでも使えるようになるので大規模開発には重要ですね。一斉同報的な処理とか簡単にできるあたりも好印象で、コネクション毎に処理が分断されている(?)みたいなので無駄に並列処理を意識せずにチャットアプリが簡単に作れるのがいい感じです。

Cysharp/MessagePipe を試してみた。

github.com

tech.cygames.co.jp

感想

ブログにも書いてある通り prism の eventAggregator より速いので、今後は prism の代わりにこっちを使ってもいいかなぁと思いました。

App.xaml

<Application x:Class="MessagePipeTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MessagePipeTest"
             Startup="Application_Startup"
             >
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.cs

using System;
using System.Windows;
using MessagePipe;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace MessagePipeTest2
{
    public partial class App : Application
    {
        IHost host;

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            host = CreateHostBuilder().Build();

            var vm = host.Services.GetRequiredService<MainWindowVM>();
            var w = host.Services.GetRequiredService<MainWindow>();

            w.DataContext = vm;
            w.Closed += W_Closed;
            w.Show();

            host.RunAsync();
        }

        private void W_Closed(object sender, EventArgs e)
        {
            // Windowをクローズしてもすぐにワーカーが終了しないので強制的に終了させる
            host.Dispose();
        }

        static IHostBuilder CreateHostBuilder()
        {
            return Host.CreateDefaultBuilder()
                .ConfigureServices((ctx, services) =>
                {
                    services.AddMessagePipe();
                    services.AddHostedService<Worker>();
                    services.AddSingleton<MainWindowVM>();
                    services.AddSingleton<MainWindow>();
                });
        }
    }
}

MainWIndow.xaml

<Window x:Class="MessagePipeTest2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MessagePipeTest2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <TextBlock Text="{Binding Value}" />
        <TextBox Background="Gray" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" Height="400"
                 Text="{Binding Text}"
                 TextChanged="tbox1_TextChanged"/>
    </StackPanel>
</Window>

MainWIndow.cs

using System;
using MessagePipe;
using Prism.Mvvm;

namespace MessagePipeTest2
{
    class MainWindowVM : BindableBase
    {
        private int _value;
        public int Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        ISubscriber<MyEvent> subscriber;
        readonly IDisposable disposable;

        public MainWindowVM(ISubscriber<MyEvent> subscriber)
        {
            this.subscriber = subscriber;

            var bag = DisposableBag.CreateBuilder();

            this.subscriber.Subscribe(x =>
            {
                Value = x.Value;
                Text = x.Text;
            }).AddTo(bag);

            disposable = bag.Build();
        }

        void Close()
        {
            disposable.Dispose();
        }
    }
}

MainWindowVM

using System;
using MessagePipe;
using Prism.Mvvm;

namespace MessagePipeTest2
{
    class MainWindowVM : BindableBase
    {
        private int _value;
        public int Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        ISubscriber<MyEvent> subscriber;
        readonly IDisposable disposable;

        public MainWindowVM(ISubscriber<MyEvent> subscriber)
        {
            this.subscriber = subscriber;

            var bag = DisposableBag.CreateBuilder();

            this.subscriber.Subscribe(x =>
            {
                Value = x.Value;
                Text = x.Text;
            }).AddTo(bag);

            disposable = bag.Build();
        }

        void Close()
        {
            disposable.Dispose();
        }
    }
}

MyEvent.cs

namespace MessagePipeTest2
{
    public class MyEvent
    {
        public int Value { get; set; }

        public string Text { get; set; }
    }
}

Worker.cs

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MessagePipe;

namespace MessagePipeTest2
{
    class Worker : BackgroundService
    {
        IPublisher<MyEvent> publisher;

        public Worker(IPublisher<MyEvent> publisher)
        {
            this.publisher = publisher;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            int i = 0;
            string s = "";
            while (!stoppingToken.IsCancellationRequested)
            {
                publisher.Publish(new MyEvent()
                {
                    Value = i,
                    Text = s,
                });
                i++;
                s += DateTime.Now.ToString() + "\n";
                await Task.Delay(1, stoppingToken);
            }
        }
    }
}

Cysharp/ZString を試してみた

github.com

Cygames の子会社 Cy# さんが出してる C#OSSライブラリ。メモリ消費量が少なく、早い(?)

tech.cygames.co.jp

細かい内容は上の Cy# さんのブログに書かれています。

試したコード

using System;
using System.IO;
using Cysharp.Text;

namespace ZStringTestDotnetCore
{
    class Program
    {
        static void Main(string[] args)
        {
            var ts1 = MeasureTask(ZStringConcat);
            var ts2 = MeasureTask(MemoryStreamStringFormat);
            var ts3 = MeasureTask(ZStringFormatUtf8);
            var ts4 = MeasureTask(ZStringFormatUtf16);
            var ts5 = MeasureTask(MemoryStreamZStringFormat);

            Console.Write($"- ZString(Concat)   {ts1}\n");
            Console.Write($"- Memory Stream   1 {ts2}\n");
            Console.Write($"- ZString(Format) A {ts3}\n");
            Console.Write($"- ZString(Format) B {ts4}\n");
            Console.Write($"- Memory Stream   2 {ts5}\n");
        }

        static TimeSpan MeasureTask(Action func)
        {
            var sw = new System.Diagnostics.Stopwatch();

            sw.Reset();
            sw.Start();
            func();
            sw.Stop();
            return sw.Elapsed;
        }

        static void ZStringConcat()
        {
            int i = 0,j = 0,k = 0;
            using (var sb = ZString.CreateUtf8StringBuilder())
            {
                while (i < 300000)
                {
                    var now = DateTime.Now;
                    sb.Append(ZString.Concat("i:", i, ",j:", j , ",k:", k, "\n"));
                    i++;
                    j += 2;
                    k += 3;
                }
                sb.WriteToAsync(Console.OpenStandardOutput()).Wait();
            }
        }

        static void MemoryStreamStringFormat()
        {
            int i = 0,j = 0,k = 0;
            using(var ms = new MemoryStream())
            {
                using (var wr = new StreamWriter(ms))
                {
                    while (i < 300000)
                    {
                        var now = DateTime.Now;
                        wr.Write(string.Format("i:{0},j:{1},k:{2}\n", i, j, k));
                        i++;
                        j += 2;
                        k += 3;
                    }
                    wr.Flush();
                    ms.WriteTo(Console.OpenStandardOutput());
                }
            }
        }

        static void ZStringFormatUtf8()
        {
            int i = 0, j = 0, k = 0;
            using (var sb = ZString.CreateUtf8StringBuilder())
            {
                while (i < 300000)
                {
                    var now = DateTime.Now;
                    sb.Append(ZString.Format("i:{0},j:{1},k:{2}\n", i, j, k));
                    i++;
                    j += 2;
                    k += 3;
                }
                sb.WriteToAsync(Console.OpenStandardOutput()).Wait();
            }
        }


        static void ZStringFormatUtf16()
        {
            int i = 0, j = 0, k = 0;
            using (var sb = ZString.CreateStringBuilder())
            {
                while (i < 300000)
                {
                    var now = DateTime.Now;
                    sb.Append(ZString.Format("i:{0},j:{1},k:{2}\n", i, j, k));
                    i++;
                    j += 2;
                    k += 3;
                }
                Console.WriteLine(sb.ToString());
            }
        }

        static void MemoryStreamZStringFormat()
        {
            int i = 0, j = 0, k = 0;
            using (var ms = new MemoryStream())
            {
                using (var wr = new StreamWriter(ms))
                {
                    while (i < 300000)
                    {
                        var now = DateTime.Now;
                        wr.Write(ZString.Format("i:{0},j:{1},k:{2}\n", i, j, k));
                        i++;
                        j += 2;
                        k += 3;
                    }
                    wr.Flush();
                    ms.WriteTo(Console.OpenStandardOutput());
                }
            }
        }
    }
}

結果

- ZString(Concat)   00:00:02.9890532
- Memory Stream   1 00:00:02.6363530
- ZString(Format) A 00:00:02.6669643
- ZString(Format) B 00:00:03.3741640
- Memory Stream   2 00:00:02.6118988

私の試したコードではあまり早くなりませんでした。MemoryStream + StreamWriter のほうがちょっと早いかなという感じ。ここでは記載していませんが、メモリ消費量もあまり違いがない感じでした。もしかすると、ZString は Unity との親和性に重きを置いているので、もしかするとこういうコードではあまり恩恵は受けれないのかもです。

ただ、StringBuilder と比較すると WriteToAsync() メソッドがあって便利ですし、 UTF8用のBuilderもあるので、そちらも便利そうです。

Flutter に入門したい

あらすじ

Flutter がラズパイでも動作することを知り、何か IoT 的なものが作れないかを検討したいので、 Flutter に入門してみようと思う。

チュートリアル

Google CodeLabs の 「初めての Flutter アプリの作成 」 の パート1 と パート2 はとっかかりとしてはよさそうだったし、日本語だったのでそちらをやってみた。

codelabs.developers.google.com

codelabs.developers.google.com

時計の表示

現在時刻をリアルタイムで表示するにはどうしたらいいか。というのが気になったので、以下を参考に作成。

flutter.keicode.com

今後

medium.com

qiita.com

www.flutter-study.dev

adventar.org

このあたりを見ながら、見繕って学習していきたい。

sony/flutter-embedded-linux を使ってみた Raspberry Pi 4 (arm64) 編

あらすじ

github.com

上記のリポジトリRaspberry Pi 4 (aarch64) 向けにビルドして、DRMで実行します。 flutter-embedded-linuxlibflutter_engine.so を使いますが、プラットフォーム毎にビルドしないといけません。ただし、いまのところ Raspberry Pi 4 本体ではビルドできなさそうだったので、linux-x64 をホストにしてクロスビルドします。

Raspberry pi (セットアップ~ engine バージョン取得)

libflutter_engine.so を作成するために一旦flutterをインストールして、その中の engine.version ファイルの中身を取得します。

セットアップ

  • SDカードにイメージを焼いておく。今回使用したイメージは ubuntu-20.04.2-preinstalled-server-arm64+raspi.img です。

  • SDカードをRaspberry PI に挿して起動するとログインを求められます。 ubuntu / ubuntu でログイン可能です。

  • パッケージをアップデートして、必要なパッケージをインストールします。

$ sudo apt update
$ sudo apt -y upgrade
$ sudo apt -y unzip
$ sudo apt install -y clang cmake build-essential pkg-config libegl1-mesa-dev libxkbcommon-dev libgles2-mesa-dev
  • flutter をセットアップして engine.version を表示します。
$ git clone https://github.com/flutter/flutter
$ sudo mv flutter /opt/
$ export PATH=$PATH:/opt/flutter/bin
$ flutter config --enable-linux-desktop
$ flutter doctor
$ cat /opt/flutter/bin/internal/engine.version

ここで取得できるバージョンを .gclient で使用します。 2021年5月22日 17:00 現在、 3fa3eb3a60e0dd741da98fe117977e7e2059042f でした。

ホスト(セットアップ~libflutter_engine.so作成)

今のところ、Raspberry PI では libflutter_engine.so を作成できない(やり方がわからない)ので、linux-x64ベースのホストPCで作成します。今回はバーチャルマシンにubuntu-20.04をインストールしてその中で作成します。今回使用した ubuntu のイメージは ubuntu-20.04.2.0-desktop-amd64.iso です。

  • パッケージをアップデートして、必要なパッケージをインストールします。
$ sudo apt update
$ sudo apt -y upgrade
$ sudo apt install -y git curl vim virtualenv python2
$ sudo apt install -y clang cmake build-essential pkg-config libegl1-mesa-dev libxkbcommon-dev libgles2-mesa-dev
  • .glient を作成します。 engine.version のバージョンを url に指定します。
solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "https://github.com/flutter/engine.git@3fa3eb3a60e0dd741da98fe117977e7e2059042f",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]
  • libflutter_engine.so を作成します。途中の ./flutter/tools/gnGOMA usage was specified but can't be found, falling back to local builds. Set the GOMA_DIR environment variable to fix GOMA. とか出るけど、無視して進めます。
$ gclient sync
$ cd src
$ ./flutter/tools/gn --target-os linux --linux-cpu arm64 --runtime-mode release --embedder-for-target --disable-desktop-embeddings
$ ninja -C out/linux_release_arm64

ssh の設定 と libflutter_engine.so の転送

Raspberry PI

ssh サーバーをインストールします。

$ sudo apt -y install ssh
$ sudo systemctl start ssh
$ ip addr

ホスト

libflutter_engine.so を転送します

$ scp ~/src/out/linux_release_arm64/libflutter_engine.so ubuntu@<raspberry_ipのIPアドレス>:/home/ubuntu/

flutter-embedded-linux をビルド (DRM編)

$ cd
$ sudo apt install libdrm-dev libgbm-dev libinput-dev libudev-dev libsystemd-dev 
$ git clone https://github.com/sony/flutter-embedded-linux
$ cd flutter-embedded-linux
$ mkdir build
$ cd build
$ cmake -DUSER_PROJECT_PATH=examples/flutter-drm-gbm-backend ..
$ cp ~/libflutter_engine.so .
$ cmake --build .

flutter-drm-gbm-backend の実行

flutter サンプルアプリを作成

$ sudo apt -y install ninja-build libgtk-3-dev
$ flutter create sample
$ cd sample/
$ flutter build linux
$ cd ..

ubuntu 20.04 server は DRM がデフォルトで機能していないようです。 /boot/firmware/usercfg.txt に以下の行を追記します。

dtoverlay=vc4-fkms-v3d

そのあといったん再起動した後、以下のコマンドを実行します。

$ cd flutter-embedded-linux/build
$ sudo LD_LIBRARY_PATH=. ./flutter-drm-gbm-backend --bundle=./sample/build/linux/arm64/release/bundle/

これで、画面上いっぱいに flutter アプリが表示されたはずです。

以上!

参考資料

RaspberryPi3B+にUbuntu 20.04 LTS (Server)の64bit版をインストール - Qiita

Bug #1896164 “Raspberry Pi 4 GPU is disabled by default - no KMS...” : Bugs : linux-firmware-raspi2 package : Ubuntu

sony/flutter-embedded-linux を使ってみる

要約

github.com

sony/flutter-embedded-linuxDebian 10 で使ってみた。

参考資料

環境

パッケージインストール

いくつかいらないパッケージもあるかもだけど

$ sudo apt -y install libnss-resolve libnss-systemd dbus-user-session policykit-1
$ sudo apt -y install weston xwayland xfonts-base fonts-vlgothic fonts-ipafont poppler-data
$ sudo apt -y install vim
$ sudo apt -y install git curl virtualenv
$ sudo apt -y install clang cmake build-essential pkg-config libegl1-mesa-dev libxkbcommon-dev libgles2-mesa-dev
$ sudo apt -y install libwayland-dev wayland-protocols
$ sudo apt -y install libgtk-3-dev ninja-build
$ sudo apt -y install python2

weston.ini

今回はもしかしたら必要ないかも?

[core]
xwayland=true
require-input=false
backend=drm-backend.so
modules=systemd-notify.so
use-pixman=true

[keyboard]
keymap_layout=jp

[shell]
#client=/usr/bin/weston-terminal
locking=false
animation=fade
allow-zap=true

Flutter Engine embedder のビルド

ビルドツールのインストール

$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=$PATH:~/depot_tools
$ virtualenv .env -p python2
$ source .env/bin/activate

.gclient ファイルの作成

最新バージョンを使用

solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "https://github.com/flutter/engine.git",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

ソースファイルを取得

$ cd ~
$ gclient sync
$ cd src
$ ./flutter/tools/gn --runtime-mode release --embedder-for-target
$ ninja -C out/host_release
$ cp ~/src/out/host_release/libflutter_engine.so /usr/lib

flutter-embedded-linux のビルド

$ cd ~
$ git clone https://github.com/sony/flutter-embedded-linux
$ cd flutter-embedded-linux
$ mkdir build
$ cd build
$ cp ~/src/out/host_release/libflutter_engine.so .
$ cmake -DUSER_PROJECT_PATH=examples/flutter-wayland-client ..
$ cmake --build .

Flutter アプリのビルド

Flutter のインストール

$ cd ~
$ git clone https://github.com/flutter/flutter
$ sudo mv flutter /opt/
$ export PATH=$PATH:/opt/flutter/bin
$ flutter config --enable-linux-desktop
$ flutter doctor

Flutter アプリのビルド

$ cd ~/flutter-embedded-linux
$ flutter create sample
$ cd sample/
$ flutter build linux
$ cd ..

Flutter アプリの実行

$ cd ~/flutter-embedded-linux/build
$ ./flutter-client ./sample/build/linux/x64/release/bundle

起動画面

f:id:bamch0h:20210417161915p:plain

まとめ

今回は Debian 10 で sony/flutter-embedded-linux を実行してみました。githubのREADMEに従ってやれば基本的には詰まることなく表示できました。