RedisGraphでアイマスのGraphを作ってみる
はじめに
RedisGraphどころかRedisすら触ったことなかったけど、QuickStartのクエリを見様見真似でやったのでそこまで詳しくないです。
でも、RedisGraphの日本語記事が無かったんで試しに使ってみた。みたいな記事です。
RedisGraphとは
公式サイトにあるPrimary featuresを見ていきましょう。
- Property Graph Modelに基づいてる。
たぶんこんな感じの、ノードとノードの間に意味を持つエッジ(ブランチ)があるグラフのことでしょう。
- ノードとエッジは属性を持つこともできる。
im@sparqlのように、あらゆる事象をエッジとノードで表すのではなく、
このように、ノードにある程度の情報を持たせても良いらしい。
im@sparql(RDF)では名前の文字列を与えるにしてもエッジを用意していたので、これは結構助かるかも?でも、属性にするか、別ノードにするかはルールが要るかもですね。 - ノードに名前が付けられる。
付けられます。 - エッジには
type
を付けられる。
エッジにはいろいろ付けられます。 - グラフはスパース隣接行列で表現される。
スパース行列は、疎行列。隣接行列はグラフのエッジを行列で表すアレです。
使ってみたレベルの今は内部構造は気にしません。 - クエリ言語はCypherです。
Graph query language(GQL) standardへの取っ掛かりとなる直感的な言語らしいです。
まぁ確かに、
CREATE (:Idol {name:'如月千早'})-[:BelongTo]->(:Production {name:'765プロダクション'})
となり、上の図を形まで同じように記号で書き起こした感じ。
書くのは煩雑かもだけど、初学者でも読み取りやすいですね。
グラフを書いてみた
im@sparqlにアイドルとユニットの所属情報を問い合わせて、結果をredisgraphに書き込むやつです。Rustで書いてます。
これ何も難しいことしてないです...結果
{ "units": { "type": "literal" , "value": "ARCANA,ルナ,ハニーサウンド,Engage!,TORICO,ミッシングムーン,you-i,765ProAllstars,エターナルハーモニー,SLEEPING BEAUTY,765MillionStars,花鳥風月" } , "idol": { "type": "literal" , "value": "如月千早" } }
を分解して
"CREATE (:Idol {{name:'[アイドル名]'}})-[:memberOf]->(:Unit {{name: '[ユニット名]'}})"
上のやつにこんな感じで入れてるだけです。↓
for element in json { let units = element.units.value; let idol = element.idol.value; for unit in units.split(separator) { let create_query = format!( "CREATE (:Idol {{name:'{}'}})-[:memberOf]->(:Unit {{name: '{}'}})", idol, percent_decode(unit.as_bytes()) // ユニット名はエンコードされてるかも .decode_utf8() .unwrap() .to_string() .replace("'", "\\'") ); result = graph.mutate(&create_query); if !result.is_ok() { return result; } } println!("{}", idol); }
グラフに問い合わせてみた
redisgraphのQuickStart通り、redis-cli
コマンドを使って問い合わせます。
ここで注意なのが、--raw
オプションを付けなければ日本語が文字化けするということです。
というわけでクエリはこちら
GRAPH.QUERY imasparql "MATCH (i:Idol)-[:memberOf]->(u:Unit) WHERE i.name = '如月千早' RETURN i.name, u.name"
クエリは説明しなくてもなんとなく分かるよね?(逆に言えばなんとなく分かる程度にしか自分も説明できない...) 結果はこちら
i.name u.name 如月千早 ARCANA 如月千早 ルナ 如月千早 ハニーサウンド 如月千早 Engage! 如月千早 TORICO 如月千早 ミッシングムーン 如月千早 you-i 如月千早 765ProAllstars 如月千早 エターナルハーモニー 如月千早 SLEEPING BEAUTY 如月千早 765MillionStars 如月千早 花鳥風月 Query internal execution time: 1.649300 milliseconds
わぁ、羅列!
--raw
さえ付けなければ
127.0.0.1:6379> GRAPH.QUERY imasparql "MATCH (i:Idol)-[:memberOf]->(u:Unit) WHERE i.name = '黛冬優子' RETURN i.name, u.name" 1) 1) "i.name" 2) "u.name" 2) 1) 1) "\xe9\xbb\x9b\xe5\x86\xac\xe5\x84\xaa\xe5\xad\x90" 2) "Straylight" 3) 1) "Query internal execution time: 0.962100 milliseconds"
こんな感じで、塊ごとに分けてもらえるのですが....
まぁ、コマンドラインでやる人なんていないでしょうし、ソースコード上で使うクライアント次第じゃないでしょうか。
おわりに
使ってみた程度としてはこんな感じです。
今回はCREATE
とMATCH
をメインに使いましたが、他にも命令はあるみたいなのでもっと便利になるとは思います。
おまけ
GitHubのリポジトリを見て気付いたかもしれませんが、redisgraphにim@sparqlのアイドルとユニットの所属情報を書き込んでくれるDocker Imageを配布しています。
コンテナ生成時にim@sparqlへ問い合わせるので、生成しなおせば新鮮なグラフになります。
ユニット情報だけでなく、いろいろ追加していこうかと思っているので、im@sparqlでは速度に不満がある方はぜひ使ってみてください。
UE4.24時代のimposter製作方法
まえがき
おそらくの話ですが、昔の頭良い人は考えました。
「遠景オブジェクトはどうせ見える角度が変わらないんだから、板ポリで表現すれば良いじゃん。」
そんなわけで、以下のように負荷軽減策として使われるようになりました。
以上、Epic Zen Gardenの紹介でした。
めでたしめでたし。
Imposterとは
日本語に直訳すると「詐欺師」らしいですが、UE4では遠景オブジェクトを表現する手法の一つとして存在しています。
「まえがき」では、見える角度が変わらないオブジェクトに対して板ポリで表現するということを紹介しましたが、UE4のImposterは一味違います。カメラの位置に対して、パラパラ漫画のように板ポリに表示する画像を変えることで、ポリゴン数を抑えつつもどのような向きにも対応できるオブジェクトが表現できます。つまり、こう↓
Imposter pic.twitter.com/lTqolmemtu
— croMisaP (@croMisa) May 9, 2020
向きが断続的なことと、真上真下は若干怪しい感じでしたが、遠景だったり大量に出ている等の負荷削減のための場所に適用すれば気付かれにくいでしょう。
本家本元であるEpicGamesはFortnite内で、遠景の木などにしようしてるらしいですね。
Imposterの作り方
基本的には先程貼った
3D Imposter スプライトをレンダリングする | Unreal Engine Documentation
や、ヒストリアさんの
[UE4] Imposterを使ってみる(多視点対応のビルボード)|株式会社ヒストリア
を参考にすれば大丈夫です。
しかし、とある一点について注意が必要で、UE4.24では上手くいきません。
DecalMaskが無い問題
どちらの手順でも、CaptureするG Bufferの選択でDecal Mask
を選択しています。
これは当たり前の話で、板ポリにテクスチャを貼るので余白をMaskするために出力します。
しかし、出力できません。他のものは出力されますが、DecalMaskは出力されません。
なんでだろうと思い、探しましたらありました。
どうやら2017年の2月末のバージョンのではもう、DecalMaskは無いようです。
出力できないからSceneDepthを使ってねとあります。
んじゃ使うか~と思いきや、SceneDepthもありません。
答え
おまじないコマンドke * rendertextures
を、
r.BufferVisualizationOverviewTargets BaseColor,SceneDepth
とHighResShot N
(Nは高精細スクショの倍数)にしましょう。
解説
おまじないコマンドke * rendertextures
を簡単に見ていきます。
ke
というのはKismetEvent
の略称でBlueprintのイベントを呼び出すコンソールコマンドです。
どのBlueprintに対してかを次に入力するのですが、*
とすることで全てのBlueprintに対して送ります。
その次がBlueprintのイベント名でrendertextures
というイベントを呼び出します。
参考
[UE4] コンソールコマンドの使い方&よく使うコマンド一覧|株式会社ヒストリア
rendertextures
イベントを持つのは、GameModeに定められているRenderToTexture_Pawn
です。
Event Graphを見ると、rendertextures
イベントは以下の2つを実行しているだけだと分かります。
このGenerator
というのは、Levelに配置して設定を行うRenderToTexture_LevelBP
のことです。
BufferCommand
r.BufferVisualizationOverviewTargets BaseColor,SceneDepth
側ですね。
RenderToTexture_LevelBP
のSetBufferCommand
関数を見ると、チェックを付けるとr.BufferVisualizationOverviewTargets
の後ろにG Bufferを追加していく様子が分かります。そして、この追加される文字列にはSceneDepthが無いことも分かります。
つまり後ろに撮りたいG Bufferを繋げれば良いので、r.BufferVisualizationOverviewTargets BaseColor,SceneDepth
となります。
Normalも欲しければr.BufferVisualizationOverviewTargets BaseColor,SceneDepth, WorldNormal
となるでしょう。
ShotCommand
HighResShot N
側です。同じくSetBufferCommand
関数にあります。
なんやかんやで数値を決めていますが、つまり自分の欲しい解像度の倍数を入れれば良いです。
参考
Taking Screenshots | Unreal Engine Documentation
おわりに
自動化されている部分がバージョンアップに追いついていないので、手作業でやる方法を示しました。
EngineContentを変えちゃっても良いかな。っていう人は、DecalMaskをチェックしたときにSceneDepth
が追加されるようにすれば良いのではないでしょうか。
まぁこれで、ようやくUE4.24でもImposterが書き出せるということで。
え?!UE4.25ってもうリリースされちゃったの?!?!?!?!
(Rust + Rocket + Tera) × プロデュース = im@sparql_details
タイトル通り、Rust言語とそのCrateであるRocket、テンプレートエンジンのTeraを使ってim@sparqlのdetailsのアレ(特に名前がないゆえの表現)を置き換えました。
Rust
Rust言語については特に大丈夫ですかね。
コンパイル時のチェックが厳しいでお馴染みのシステムプログラミング言語ですね。
C++に足を浸けてる人間としては、今後学びたい言語でした。
ja.wikipedia.org
Rocket
Rustで使えるWebフレームワークです。初学者にも満たないので、Webフレームワークって何ができればWebフレームワークなのか分かりませんが、ルーティングとかフォーム入力とか当たり前の機能はあるみたいです。
なんだこの頭悪い説明は...
Tera
Rocketで使えるテンプレートエンジンの一つです。
Inspired by Jinja2 and Django templates
ですって。
やったこと
最初にも例出したIchikawa_Hinana detailの実装を置き換えました。
今まではNode.jsとexpressだったのですが、Rustを勉強するために差し替えた次第です。
なお、見た目は全く変わってないのでユーザーには特に益はありません。
内容
Rustでも非常に簡単に書けました。126行のうち、半分くらいはJSONのデシリアライズなのでめっちゃ少ないです。
#[derive(Debug, Deserialize, Serialize)] struct N { r#type: String, value: String, } #[derive(Debug, Deserialize, Serialize)] struct O { r#type: String, #[serde(default)] datatype: String, #[serde(default, rename = "xml:lang")] xml_lang: String, value: String, } #[derive(Debug, Deserialize, Serialize)] struct Bindings { n: N, o: O, } #[derive(Debug, Deserialize)] struct Results { bindings: Vec<Bindings>, } #[derive(Debug, Deserialize)] struct Head { vars: Vec<String>, } #[derive(Debug, Deserialize)] struct Response { head: Head, results: Results, } #[derive(Serialize)] struct MessageContent { title: String, num: usize, json: Vec<Bindings>, }
im@sparqlのJSONの結果を見たことある人は納得の構造体群ですね。
ちなみにクエリは
PREFIX schema: <http://schema.org/> PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/> SELECT * WHERE { { imas:{} ?n ?o;} }order by (?n)
です。{}
には適切な主語が入ります。
処理部(展開可)
#[get("/<subject>")] fn get_data(subject: String) -> Template { const FRAGMENT: &AsciiSet = &CONTROLS; let encoded_subject = utf8_percent_encode(&subject, FRAGMENT).to_string(); let quety = format!("PREFIX schema: <http://schema.org/>PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>SELECT * WHERE {{ imas:{} ?n ?o;}}order by (?n)", encoded_subject); let encoded_query = form_urlencoded::Serializer::new(String::new()) .append_pair("output", "json") .append_pair("force-accept", "text/plain") .append_pair("query", &quety) .finish(); let base_url = format!( "https://sparql.crssnky.xyz/spql/imas/query?{}", encoded_query ); let res = ureq::get(&base_url).call(); if res.ok() { let json_str = res.into_string().unwrap(); let res_json: Response = serde_json::from_str(&json_str).unwrap(); let mut json = res_json.results.bindings; let json_num = json.len(); if json_num > 0 { for data in &mut json { match &*(data.n.value) { "http://schema.org/memberOf" => { data.o.value = percent_decode(data.o.value.as_bytes()) .decode_utf8() .unwrap() .to_string(); } "http://schema.org/owns" => { data.o.value = percent_decode(data.o.value.as_bytes()) .decode_utf8() .unwrap() .to_string(); } _ => (), } } let content = MessageContent { title: subject, num: json_num, json: json, }; return Template::render("detail", &content); } } let content = MessageContent { title: subject, num: 0, json: vec![], }; Template::render("error", &content) }
キモなのは#[get("/<subject>")]
とfn get_data(subject: String)
でしょうか。いわゆるルーティングの文字列を引数として取得しています。これをすることで、URLhttps://sparql.crssnky.xyz/imasrdf/RDFs/detail/
の後ろに主語が続くと適切に処理されます。
引数として取得した文字列は、そのままクエリの主語の部分に連結されます。そしてクエリを送って結果をデシリアライズします。
im@sparql特有の処理として、for文の部分があります。一部の主語は日本語をそのままURLエンコードしたものをつっこんでいるので、表示には適しません。なので、テンプレートエンジンに送る前にデコードしてあげています。
それ以外はそのままのJSONをテンプレートに渡して描画しています。
こんな感じで、簡単にWebフレームワークを使ったRustを書けます。みなさんも安全性に守られたコーディングしませんか。
DECLARE_DYNAMIC_MULTICAST_DELEGATEとC++
ちょっとしたお話
みんな大好き動的マルチキャストデリゲート
マルチキャスト デリゲート | Unreal Engine ドキュメント
動的デリゲート | Unreal Engine ドキュメント
個人的な主観では、C++でグイッと処理したあとにBPに結果を渡すときによく使うんですけど、C++とBP両方にイベントを発行したい時もあります。
BPであればBindノードを使うことでイベントを登録できます。
C++であれば、動的デリゲートの説明にあるようにAddDynamic
を使って関数を登録します。
でも、VisualStudioのIntellisenseにはAdd
やAddUnique
くらいしか出てきません。
なんでやろなぁ
動的デリゲートの説明をよく見たらヘルパーマクロ
って書いてありました。
そうです、マクロなんです。
Delegate.h
に#define AddDynamic(....
となってました。
というわけで備忘録
週刊IM@Study Vol.4 と アイマスハッカソン2019の続き
こちらはアイドルマスター Advent Calendar 2019の10日目の記事です。
12/5のアイマスハッカソン2019にご参加いただいたみなさんありがとうございました。
そして、関東・関西のスタッフさん、お疲れ様でした!
10時から行われていた本イベントですが、18時からの成果発表・プロデューサーLT大会の様子はこちらでご確認いただけます。
アイマスハッカソン2019 in 東京&関西 成果発表・プロデューサーLT
そこでは週刊IM@Studyの寄稿内容の概要について発表しました。僕が発表してる部分はこちら↓
https://youtu.be/SMEKvULOnvc?t=4055
発表している対象についてはこちらを見てもらったほうが早いかもしれません。
後半のアレの部分ですけど、試聴範囲を超えてしまうので冒頭で代用 pic.twitter.com/VRw3U0pZou
— croMisaP (@croMisa) 2019年10月27日
「UE4でアイマス楽曲をさらに盛り上げてけ☆」と題しまして、UE4で楽曲のビジュアライズ的なことをしています。
こんな感じでUE4の簡単な説明をし、操作方法を学びつつ詳しく編集を説明しています。
今回はUE4のエディタ機能のみを使用しているため、C++を1字も書きません。その代わりにBlueprintを引いてもらいます。世の中の評価としても、エンジニア以外にも簡単に使えるというものということで普及しており、フローチャートとだいたい同じと考えても良いと思います。
もちろん、C++に慣れた方には少し煩雑かもしれませんし、C++と比べて関数呼び出しが死ぬほど遅いといったデメリットもあります。しかし、小さなプロジェクトであったり、大きなプロジェクトでも用法を正しく使えば開発イテレーションを大幅に上げることのできる素晴らしいツールです。
そんなUE4のハンズオンや他3本の記事、ミニコラム3本が詰まった100ページに及ぶ週刊IM@Study Vol.4は、第2回技術書同人誌博覧会で頒布されるので、ぜひお手にとってみてください。Vol.3の時にあった電子決済に引き続き対応している他、電子版も頒布されるので、100ページの本は...という方もぜひどうぞ!
さてお話は変わり。
Youtubeコメでもありましたが、今回はスライドソフトにUE4を使用しています。
UE4上でスライドを表示することで、シームレスにデモへ移行することができました。
スライド自体は前日に作っており、当日はそのシステムを作ってたので今回はそれについてもお話しようかと思います。
Step 0 スライドを作る
なんの変哲もないです。僕はPowerPointで作りました。
アイマスハッカソン2019in関西 - Speaker Deck
作り終えたら、各ページを画像出力してください。パワポにはそういう機能あるし、Webサービス使わなくて良き。
Step 1 UE4に取り込む
テクスチャとして取り込みます。なんとなくそんな気分なので、Mipmapは作りません。
Step 2 Materialを作る
テクスチャパラメータのノードを一つ持つMaterialを一つ作ります。
ハッキリ見えてほしいので、Unlitです。
Step 3 スライドBPを作る
ConstructionScriptで全部仕上げます。
InstanceEditableなInt型変数からどのTextureObjectを使うかを分岐し、Step2で作ったMaterialからMaterialInstanceDynamics(MID)を作ってそこにTextureObjectを投入します。
ConstructionScriptで済ますことで、Levelに配置した後でもぐりぐり~って変えられます。
投稿用 pic.twitter.com/UwtQUFMK19
— croMisaP (@croMisa) 2019年12月10日
Step 4 スライドLevelを作る
逆に言えばConstructionScriptで作っちゃったので、Levelが開始してしまったら変更ができません。せっかくなので、LevelStreamingを試してみたいし、良い感じにしましょう。
まず、テキトーにスライドBPが一つだけあるレベルを量産します。各BPは順番に四角の各辺をなぞるように配置します。
- Slide1はx,y,z=1000,0,0・Pitch,Yaw,Roll=0, 0, 0
- Slide2はx,y,z=0,1000,0・Pitch,Yaw,Roll=0, 90, 0
- Slide3はx,y,z=-1000,0,0・Pitch,Yaw,Roll=0, 180, 0
- Slide4はx,y,z=0,-1000,0・Pitch,Yaw,Roll=0, 270, 0
みたいな感じ
スライドの数だけ...スライドの数だけ....多い....
Step 5 LevelStreamingを整える
"HackathonPersistant"的な名前のパーシスタントレベルを作り、Levelsウィンドウで下にぶら下げます。
今回は、スライド以外にもデモ用のレベル(Test
)もぶら下げています。
Step 6 LevelBPを書く
次へキー・戻るキーを設定し、それのイベントに紐づけて次のスライドを持つレベルをその場で読み込むBPを記述します。Timeline
ノードを使えば、良い感じにカメラを回すことも出来ます。
Step 7 試す
LevelStreamingによって、必要なスライドのみが違和感なく読み込まれてることが確認できます。とってもメモリに優しいですね!!
投稿用 pic.twitter.com/UlCOqDxpd6
— croMisaP (@croMisa) 2019年12月10日
こんな感じでアイマスハッカソン2019ではスライドを流していました。これを使い回せば、UE4で作ったものを簡単に見せられますね!
お詫び
WebRemoteControlの発表を書きたかったのですが、本日公開されたUE4.24でも難しいみたいです...
LogRemoteControl: Error: Web Remote Call deserialization error: function: /Game/Hackathon/HackathonPersistant.HackathonPersistant does not exist on object: ToNext
— croMisaP (@croMisa) 2019年12月10日
今日も通じない
UE4とRTTI ~MSVC(Windows) V.S. clang(Mac, Linux)~
RTTIとMSVC, clangの環境の違いには気をつけろの情報共有である。
背景
im@sparqlをUE4で触りたいなと思ったら、JSONが使えるようにしたかった。
そこで、ちょうどよいものを見つけたので導入した。
こいつ(cereal)は最高だ、JSONを構造体のように扱える。
いや、実際に構造体を定義して、そこにJSONを流し込んでるから当たり前だ。
これに関しては、技術書典で頒布した週刊IM@Studyの2019/04号に記載している。
当然リポジトリもある。
問題
昨年末のpaypay祭りの時に思い切って、ノートPCをWin機からMac機に切り替えた。
当然開発環境は変わり、Xcodeなのでclangとなる。
これが問題。
おうちのデスクトップPCはWin機なため、VS2019のMSVCで作業している。
こいつで動作確認をしているのだが、clangではどうもビルドが通らない。
具体的には、RTTI関連でエラーが出る。
MSVCはRTTIはデフォルトでONなので気にすることは無かったが、cerealではtypeinfo
を用いる。
clangではデフォルトでRTTIはOFFなので、エラー文で叱られてしまった。
コンパイラに従いBuild.csでbUseRTTI=true;
を指定してビルドすると見知らぬリンカエラーに遭遇した。
似た人↓
https://answers.unrealengine.com/questions/745636/undefined-symbols-for-architecture-arm64-1.html
理由
理由↓
https://answers.unrealengine.com/questions/871773/view.html
意訳すると
- LinuxではRTTIと非RTTIを混ぜちゃダメ
- モジュールごとにRTTIの有無は分けられるから、必要な部分に切り出して
- その時に、Engineのクラスを混ぜちゃダメ
- それを"ブリッジ"するラッパーを書くのが良い
- "OpenEXRWrapper"プラグインを参考にしてね
質問者も「Windowsでは動くから、変に気を取られたよ」(意訳)と、自分と同じ心境を語ってる。
現在
モジュール分割をしている。
でも上手くいかない。
C++なんもわからん
何がわからないのか分からない
RTTIとMSVC, clangの環境の違いには気をつけろの情報共有でした。
IM@Study合同本@C96 4日目(月曜日)南地区"リ"01a
なんとぉ!
週刊IM@Studyの3冊目が今夏のコミケで頒布されます。
場所はC96 4日目(月曜日)南地区"リ"01aです。
https://webcatalog-free.circle.ms/Circle/14514591
表紙絵はお馴染みになってきた文月きょうさんにお描きいただきました。
前回分はこのブログで宣伝していませんが、未来ちゃんが持っています!再帰!!(再帰ではない)
表紙構成は、Uske_Sさんです。
本業はPかつ副業で印刷系ITエンジニアらしく、レイアウトが華やかになりました!
強力な協力者です。
中身
- im@sparql応用 〜HTMLでさっと表示編〜
著者:croMisa
概要:im@sparqlを利用する、簡単なAjaxのWebページを作るハンズオンです。 - スコアを用いたライブの予習セットリスト自動生成の試み
〜THE IDOLM@STER CINDERELLA GIRLS 7thLIVE編〜
著者:遠山 賢寿
概要:創刊号で紹介されたデレマスのセットリスト自動生成、これを7thライブに対して動かし、セットリストを予想していきます。 - pLaTeXを用いた小説系同人誌作成入門
著者:MH35
概要:小説などの文章がメインとなる同人誌において、pLaTeXで作成する時の方法や入稿の注意点が紹介されます。 - 小鳥さんと一緒に100%Kotlinのサイト製作〜Backend編〜
著者:にしこりさぶろ〜
概要:Kotolin製Webアプリフレームワーク"Ktor"を、Kotlin初心者の小鳥さんと学ぶ前半とルーティングやJSONシリアライズ、GETクエリの取得を解説する後半で紹介していきます。 - 青羽美咲が業務効率化を目指してAlexaスキルを作る話
著者:きりだるま
概要:美咲ちゃんと一緒にAlexaのスキルを学び、Pの日頃のプロデュース業務(ミリシタ)を効率化するまでを描くハンズオン的ストーリです。 - プレイリスト自動生成でアイマスをより楽しむ
著者:Wakuwaku
概要:アイマス楽曲が増えてきても、いつも聴く楽曲は固定化してしまう悩みを解決するべく、NAS内に日替わりでプレイリストを自動生成する方法を紹介します。