頭ん中

しがないITエンジニアが、考えた事を書きます。

Uno PlatformのWebAssemblyでゲームを作る

Uno PlatformのWasmを試していて、タイピングみたいな簡単なゲームならできるかも?と思って完成したのがこれ。

https://typeratta.azurewebsites.net/

f:id:siamcats:20200601094526g:plain

ちなみにUWP版はこんな感じです。コードはSharedプロジェクト内にしか書いてないので、ほぼワンコードで同じ動作をするアプリのデスクトップ版・Web版ができました。 f:id:siamcats:20200601022550g:plain

コードはGitHubに。 github.com

以降、Wasmで動作させる時のポイントというか躓いたとこをいくつか。

KeyDownイベント

UWPではCoreWindowのKeyDownイベントを拾うのが一般的だと思うのですが(キーボードショートカットのビヘイビアとかでよく使う)、これがUno Platformでは未対応で、Wasmの時にキータイピングを拾えませんでした。

public MainPage()
{
    this.InitializeComponent();

    Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
}

private void CoreWindow_KeyDown(CoreWindow sender, KeyEventArgs e)
{
    //Wasmだと発生しない
}

issueには上がっていて、「UIElementなら対応してるから我慢してね!(意訳)」とあるので、仕方ないからWasmの場合だけそっちで拾うようにします。

<Page
    中略
    KeyDown="UIElement_OnKeyDown">
public MainPage()
{
    this.InitializeComponent();

    //Wasm非対応
    Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown; 
}

private void CoreWindow_KeyDown(CoreWindow sender, KeyEventArgs e)
{
    e.Handled = true;
    Keystroke(e.VirtualKey);
}

private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
    e.Handled = true;
#if __WASM__
    Keystroke(e.Key);
#endif
}

private void Keystroke(VirtualKey key)
{
     //ここにキータイピングした時の処理
}

本当はこれだとコントロールにフォーカスが当たってるときに拾えないため注意が必要。今回はFocusableなコントロールが一切無いページだからこれで良しとする。

Storyboard アニメーション

こんな感じで、タイポした時に画面を赤く点滅させるアニメーションを設定します。

<Storyboard x:Name="ErrorStoryboard">
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="0.0" To="1" Duration="0:0:0.02" AutoReverse="True"/>
</Storyboard>

UWPはこれでOKなんですが、WasmはAutoReverseが対応してないみたいで、Opacityが0→1になったっきり、真っ赤なまま戻らなくなってしまう。

それではとKeyFrame指定して設定してみますが…

<Storyboard x:Name="ErrorStoryboard">
    <DoubleAnimationUsingKeyFrames
            Storyboard.TargetName="MistakeFlashing"
            Storyboard.TargetProperty="Opacity">
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.02" Value="1"/>
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

これにも対応していないせいか、今度はそもそも全く反応しません。

最終的には、こんな感じでUWP用とWasm用のStoryboardを2つ定義して

<Storyboard x:Name="ErrorStoryboardWasm">
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="0.0" To="1" Duration="0:0:0.02"/>
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="1" To="0.0" Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Name="ErrorStoryboardUwp">
    <DoubleAnimationUsingKeyFrames
            Storyboard.TargetName="MistakeFlashing"
            Storyboard.TargetProperty="Opacity">
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.02" Value="1"/>
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

コード側から呼び出すときに呼び分けました。

//ミス
if (!result)
{
    vm.Mistake++;
#if __WASM__
    ErrorStoryboardWasm.Begin();
#else
    ErrorStoryboardUwp.Begin();
#endif
    return;
}

1つのStoryboard内で同じ要素に同じAnimationを複数定義すると、本来は同時に動いてエラーになるはずなんですが、Wasmだと順に実行されるようで上手くきました。いいのか?

雑感

Webサイトだと思うからJSを使いたくなる訳で、あまりWebの世界やりとりのない、簡単なゲームアプリ、例えば教育教材みたいなアプリならUno PlatformのWasmもなかなか勝負できるのでは、と思った。ただ、それならQtとかUnityという選択肢がある。

SPAとかPWAの延長線でWasmが語られていることを考えると、GUIアーキテクチャ(をラッピングする言語)にXAMLを掲げるUno Platformの普及は、なかなか苦しい気がする。Webも含めたフロントエンド全体から見たら、WPFやUWPのシェアというのは極僅かなものだろうから。Silverlightが生きていればなぁ。

さて、次はBlazor触るか…

API GatewayとLambdaでバイナリデータを扱う

画像などのバイナリデータをアップロードするREST APIを作ります。詳細はAmazon API Gateway 開発者ガイドのバイナリペイロードをサポートするという章を読むこと。バイナリの扱いにちょっと戸惑ったのでメモ。

前提

  • API GatewayとLambdaの基本的な設定内容は省略

API Gateway

バイナリメディアタイプ

API GatewayはデフォルトではテキストペイロードUTF-8エンコードされた JSON)として扱うため、バイナリペイロードで扱う場合は、API設定からバイナリメディアタイプを指定し、バイナリとして扱うContent-Typeを設定します。

今回はmp3をアップしたいのでaudio/mpegを指定

マッピングテンプレート

さっきの手順でバイナリペイロードとして扱うことにはなったんですが、そのままバックエンド(Lambda)までバイナリで渡せるのかなと思ったら、違ってた。

API Gatewayは、統合リクエスト本文マッピングテンプレートの設定で、バックエンドへの渡し方を決められます。ここでContent-Type:audio/mpegについてパススルーすれば、Lambdaにバイナリで渡るのかなと試したのですが、エラー。

{"message": "Could not parse request body into json: Unexpected character (\'\/\' (code 47)): maybe a (non-standard) comment? (not recognized as one since Feature \'ALLOW_COMMENTS\' not enabled for parser)\n at [Source: (byte[])\"\/9j\/4AAQSkZJRgABAQEASABIAAD\/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT\/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT\/wAARCAEqASwDASIAAhEBAxEB\/8QAHQABAAEFAQEBAAAAAAAAAAAAAAgBAgYHCQUEA\/\/EAEgQAAEDAwMDAgMFBQQHBQkAAAEAAgMEBREGByEIEjETQSJRYQkUMnGBFSNCkaEWUrHBFyYzYnKCkiRz0eHxJTRTY4OUo7Lw\/8QAHQEBAAEFAQEBAAAAAAAAAAAAAAECAwQFBgcICf\/EAC8RAQABAwMCAwYGAwAAAAAAAAABAgMEBREhMUEGBxITQlFhcYEUFZGhscEiM+H\/2gAMAwEAAhEDEQA\/\"[truncated 17128 bytes]; line: 1, column: 2]"}

結局どうやってもAPI GatewayからLambdaへはJSONオブジェクトで渡ることになるので、テキストとして処理しないと駄目みたいです。というこで、統合リクエストLambdaプロキシ統合の設定にチェックをしれて、ややこしいマッピングテンプレートの設定は省略します。

Lambda

API Gatewayからはbodyがbase64エンコードされたいつものJSONオブジェクトが来るので、それをデコードして処理します。簡単にS3に置くようなプログラムを実装すると以下。

bucket = 'xxxx'
s3 = boto3.resource('s3')

def lambda_handler(event, context):
    print(event)
    
    if event['isBase64Encoded']:
        date = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
        key = date + '.mp3'
        print(key)
        
        decode = base64.b64decode(event['body'])
        
        obj = s3.Object(bucket,key)
        res = obj.put(
            Body=decode,
            ContentType='audio/mpeg'
            )
        
        print(res)
        
        return {
            'statusCode': 200,
            'body': json.dumps(
                {
                    'message': 'uploaded.',
                    'file': key
                }
            ),
            'isBase64Encoded': False
        }
    else:
        return {
            'statusCode': 400,
            'body': json.dumps(
                {
                    'message': 'not binary file'
                }
            ),
            'isBase64Encoded': False
        }

参考にしたサイト

UWPでローカルDBのマイグレーションをする(EntityFramework Core / SQLite)

まえがき

ちょっと昔のAndroidアプリやWindowsアプリで、分岐とDDL文を散りばめたギリギリの運用を目にすることが結構ありました。

ローカルDBとテーブルをCodeFirstに生成し、とりあえずデータをCRUDする方法が紹介された記事は一杯ありますが、アプリをアップデートする際のマイグレーションまで解説されたドキュメントは、あまりないんですよね。

ということで、たぶん現在もっとも標準的と思われるWindowsアプリの構成(UWP / EntityFramework Core / SQLite)で、ローカルDBのマイグレーションを試していきたいと思います。

続きを読む

Uno Platformを試す

XAMLひとつでWindows/iOS/Android/WebAssemblyに対応という夢のような話です。 f:id:siamcats:20200206025122p:plain

Create a Single Page App with Uno

既にチュートリアルのやってみた系記事はあるので、いくつか躓いたところと、良くあるXAMLのデザインテンプレートが実際どんな感じになるか紹介したいと思います。

続きを読む

UWPクライアントから社内プロキシ認証を超えてREST APIを叩くのに嵌った

(ほんといちいちいちいち厄介な社内プロキシ)

f:id:siamcats:20200130192353p:plain

マニュフェストに忘れずチェックいれて

// using Windows.Web.Http;
var httpClient = new HttpClient();
var uri = new Uri("https://contoso.com/GetApi");
var response = await httpClient.GetAsync(uri);

こんな感じで叩けば

f:id:siamcats:20200130185243p:plain

おなじみの認証ダイアログが勝手に出てきてくれるじゃないですか。

でもなんかよくわかんない例外になる時があるんです。

"Exception from HRESULT: 0x80072EF3"
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

よくよく調べてるとアプリ起動後に最初に叩くリクエストがPOSTの時だけ起きてるみたいなんですよね。

var httpClient = new HttpClient();
var uri = new Uri("https://contoso.com/PostApi");
var content = "hogehogebody";
var response = await httpClient.PostAsync(uri,content);

なので適当にGETメソッドのリクエストを投げ認証させておいて、それからPOSTのAPIを叩けば普通にいけました。うーん…。

共働き世帯が転勤で保活(転園)&引越した話

家族背景

  • 大阪市在住の共働き夫婦(別の会社で業種も違うけど2人ともフルタイム勤務)
  • 息子は市内の保育園(年少クラス)
  • 夫婦とも実家は遠く頼れない

転勤辞令と家族会議

東京への転勤辞令(9/1付け)が出たことを同僚からのSlackで知ったのは、8月半ばのお盆休み中のことでした。

さっそく妻と今後について話し合います。もし転勤になった場合にどうするかという大凡の方針は、以前から2人で段取りを決めていたつもりでしたが、いざこうなってみると、なかなか細かいところの合意形成が大変です(揉めます)。

どうにか2人で出した結論は以下の通り。

  • 妻は会社へ東京への転勤希望を出す。
  • 私は会社へ赴任時期を調整してもらえるよう交渉する。
  • 同じタイミングで転勤できない場合、東京へ行く方は単身赴任(大阪に残る方がワンオペ)する。
  • ただしこれは上限3ヶ月として、それ以上続くようであれば、どちらかが仕事を辞める。
続きを読む

『Bullshit Jobs:クソどうでもいい仕事』

たまたま原著に目(Audibleなので耳)を通していたので。

ツイートのリプ欄は、様々な職業への蔑みのコメントで荒れて、ツイート主の藤田孝典さんは、原著の趣旨から外れることを憂いている。ただ、福祉職やその他現業職の待遇を守るという立場からの発言とは言え、自身もまたコンサルなど待遇に恵まれた職業への価値評価を疎か(もとい攻撃)しているように見えるので、身から出た錆としか思えない。

Bullshit Jobs=クソくだらない仕事

www.amazon.co.jp

あらすじ

著者の主張は確かに過激で、リサーチャーだとかマーケターだとか企業弁護士みたいなBullshit Jobなホワイトカラーは消えて構わないし、こんなのが蔓延るのは経済的な効率化を是とする資本主義の膿だと指摘します。

そしてこれらは労働者を無理にでも働かせ社会を支配せんと目論む支配階級の陰謀だと。ジョークめいたタイトルと裏腹に、かなり壮大な話です。*1

感想

ノンポリなので陰謀とか社会革新の話は正直ピンとこないのですが、僕がこの本を読んで感じたのは、自身の仕事に価値・意味を見出せるのかという労働者本人の認知の課題です。

本の中には、客観的にBullshit Jobsの定義が体系立ててまとめられていますが、私はそれよりも、自分の仕事を無駄だと思いながら続けるという主観的なBullshit Jobsの存在の方が、よっぽど哀しく、そして解決すべき問題のように思います。どうしたら自分の仕事に対して誇り・やりがいを持てるようになるのでしょうか?

著者は、Bullshit Jobsに気付かない無知で愚かな経営者に対して、未必の故意的な責任を追及しています。私も往々にして企業の経営者や管理者には労働者の認知を助ける活動が足りてないよなぁと感じることが多いです。ワークエンゲージメントとかいうやつですかね?具体的には以下のような単純な働きかけの話だと思います。

  • 労働者に仕事の価値を説明して納得してもらう
  • 労働者へ正当な評価・待遇を与える*2

これらを実践することで従業員のエンゲージメントは高まり、また無知だった経営者も現実に目を向けて真に無駄な仕事に気付く。こうやって、主観的なBullshit Jobsも、客観的なBullshit Jobsも、どちらも自然に淘汰されていく(ちょっとは減るんじゃないか)と、性善的ですが信じています。

職業に貴賤なし

最初のツイートに戻ります。職業差別とか賤業なんて言葉は前時代に置いてきたはずでしたが、現実はそうでもないようです。

営業と製造、コンサルと開発、アプリとインフラ、こんな職種間の対立に心当たりがある人は多いと思います。私もコンサルは嫌いだし、意味の無い仕事してる癖に偉そうにしやがって、と思うことはあります。

他にも公務員のように半分蔑みのように無駄を叫ばれる職種があったり、発注者と受注者、労働者と使用者みたいな本質的に利害が対立する職種なら尚更酷い対立があるでしょう。相互尊重の精神が失われたとき、偏見や差別というのはすぐそこにあります。

ところで、この人たちの仕事は本当に全部無駄なんでしょうか?無駄のない仕事をしているのでしょうか?僕にはわかりません。

仕事の価値を見出すって本当に難しいことで、自分の仕事の価値すら認識できない人が多いのに、他人の仕事の価値まで把握する余裕なんて普通は無いよなぁと思うのです。*3人間が仕事の価値をスマートに判断できないために、Bullshit Jobsや職業の貴賤なんてのが生まれるんだと思います。

それでもそんな人間の限界を憎んで、Bullshit Jobsを減らし職業に貴賎のない世の中を目指すのであれば、Bullshit Jobsや賤しい職業が何かを探し出して定義する=他人の職業を貶めるような賤しい行為はやめませんか。

*1:正直、僕のクソみたいな英語力では特に後半の理解はちょっと怪しい

*2:*おそらく藤田さんもこうした雇用者側の責任に言及しているのだと思います。

*3:あるいは自分の仕事の価値に絶対的な自信を持つあまり、周囲を低く見積もってしまうのかもしれない。自身が正当な評価を受けている実感と精神的余裕があれば、こうも攻撃的にならずに済むのに。