Imaginantia

思ったことを書きます

日記 0701

7月になってしまいました。そういえば日記書いてから1ヶ月は既に過ぎていました。

今日は今までずっとやるのを放置していたメインタスクを進めました。実装するとね。できるんだよね。

これである程度はvisualに専念できそうです。mechanicsでいろいろ考えなきゃいけないことはほぼ終わったはず。あと2個くらい残ってるけど。

やっとを作り始めることができます。つくれんのかなほんとに。

これで終わりとかでは全然ないんですけどね。まぁ出来る作業がなくなるまでやっていきましょう。

 

あとそういえばリーゼちゃんの眼を弄りました。

このリングが好きです。とても。

前の日記で眼に黒い領域が入っていると綺麗、みたいなことを書いたので黒を取り入れてみたんです。正確には暗い色。

だいぶメリハリ綺麗について文様らしくなって好きな感じになりました。謎のぽちぽちも入れたし。

いろいろ適当に描いてから色相を上から乗せて色つけようと思っていたんですが適当だと「空間的雰囲気」になっちゃって「図形の色」には見えないということを学びました。とはいえやってること自体は変わりませんけど。ちょっと細かくしただけ。

願いを叶えようとした結果望んだ眼の強さ髪の強さを手に入れることができた気がします。だいぶ気に入っています。腕のふわっふわが本当に素晴らしいという話を今日しました。

これからも割と頻度高めに使おうかなと思います。というか外行きのアバターがほしいという話を結構前に思っていたんですが、それに比較的合う形になるかもしれません。内でも使ってるとおもいます、気まぐれで。いやどうせ全ては気まぐれなんです。

メープルちゃんはすごい好きなんだけどなんでしょうねー…。服のディティールが高ければ高いほど眼のディティールを上げられるので好きな眼にできる、とかはあるかも。良い意味でToonな子なんですよね。だけど私はこういうごちゃった眼が好きなんだと思います。

そういえばこの子の服のカラーリングはこれに割と近いのかなとも思いました。勝手にそうはなるんですけど。

白色、私は結構好きなのかもしれませんね。色が色だけにあまり意識したこと無かったけど。まぁ (選択をしないという意味で)「無」に近いわけで、私が好きなものの一つであるのは確かです。

素直に私が成れるアバターが増えるのはとてもうれしいです。


さて。おまけこーなー。一昨日書いたデータ構造の話、しましょうかね。折角?なので。

一般に「複数のデータを保持したい」ことは多々あるわけですが、その在り方は場合によって様々です。

例えば「空いている装置を把握したい」とか「重なっている物体を保持したい」とか「実行するイベントを管理したい」とか。いっぱいあります。

その在り方は保持の仕方の選択に直結し、その選択はデータ構造と呼ばれます。データには構造があるのです。何故なら構造がなければ何も操作できないから。

もっと思想的な話をすると時間が掛かりそうなので現在の具体例について話してみますね。

 

まず「いくらでもデータを格納できる領域」がほしいと思います。でも C# の配列は長さを決めなければいけません。さて困った。

そこで出てくるのは「所詮有限個しか追加しない」ということ。何故なら時間は有限なので。逆に言えば、必要とされる分だけあればいいわけです。

だから「足りなかったら増やす」ということを考えたくなる。単純に考えるとこうなりそうです:

void Add(X x) {
  X[] newArray = new X[array.Length+1];
  for(int i=0;i<array.Length;i++) newArray[i] = array[i];
  newArray[array.Length] = x;
  array = newArray[i];
}

でもこれは効率良さそうには見えません。100個の要素があったら1つ追加する度に100個コピーするのです。かなしいね。

そこで知られている面白い方法が、サイズを毎回2倍にするというものです。つまり、こう。

X[] array = new X[1];
int count = 0;
void Add(X x) {
  if(array.Length == count) {
    X[] newArray = new X[array.Length*2];
    for(int i=0;i<array.Length;i++) newArray[i] = array[i];
    array = newArray;
  }
  array[count] = x;
  count++;
}

63個に1つ追加するのは一瞬です。64個に1つ追加するのは64個のコピーが入るけど、今まで32回一瞬だったことを考えれば「均して一瞬」とみなす考え方があります (償却計算量 / ポテンシャル解析とかの話)

これで好きな数だけ要素が突っ込めるようになりました。私はだいたいこれを毎回なんか複数個保持しようと思う度につくっています。本来なら Generics でぐっとやればいいのにね…。まぁ。

ちなみにどうせ私が使う例は有限も有限、高々100個なので「あるインデックスに直接書き込みたい」みたいなのもこれで対応させてます (max(count*2, index+1) を取る)。平衡二分木とか作ってられないですわ。

 

だから「ある条件を満たすものを探す」みたいなのは毎回愚直に count までループを回しています。まぁそんなに数多くないのでコレ自体は大丈夫なはずです。UdonのProfilingはまだちゃんとはやってないけど…そのうち詳細は調べます。

で、使えなくなったものを消すみたいな処理を書きたいじゃないですか。私は単純に「該当要素を見つけたら null に差し替える」ということをやったわけですが、こうするとちょっとおもしろい状況が生まれます。

count が「走査すべき領域」を示さないんですね。走査すべき長さの本当の名前は maximumIndex です。削除がなければ maximumIndex+1 == count なわけですが、削除があるとこれが崩れます。

だからここまで来たら count を消して。追加するときには全要素を前からみて空いているところにつっこむ。空いているところがなければ2倍に増やしてつっこむ。削除するときは全要素を前からみて見つけたら消す。みたいな感じで「集合」を作っています。かなしいね。

前に書いたバグの原因が結局これに因るもので、count はそれはそれで利用価値があるので残していたんだけどだからこそ混乱したということがあったわけです。本来なら整備されたデータ構造を扱えてこんなこと起こさないんだけどね。ね。

 

最後に。「実行するイベントを管理したい」場合。これは特に、順番に実行したい場合。私のケースでは毎フレーム1つのイベントを扱う機構に向かっていろんな物体がイベントを投げたりする構成になっていました。こういう構造をキューと呼ぶわけですが、その高速な実現はそこまで自明ではありません。

後ろから追加して後ろから消えていくなら「現在位置」を覚えておけばいいだけですが、前から消せるので「開始位置」と「終了位置」になるわけです。今2秒くらい考えたらマジでそのまま記録して足りなくなったら「必要な部分の2倍の大きさ」に移し替え、みたいなことをやればいいのではとか思ったけど。どうなんだろう。

うーん。それで十分な気がしてきた。まぁなんか私は謎の実装をやったというだけなんですけど。

まぁ、一回やってみたかったということで。「取り出すときには前後反転した列を作っておく」っていう面白構造です (Banker's Queue)。あれは cons ベースだから素直なんだろうな。

 

そんなこんなでUdonでいろんな情報保持のために毎回データ構造を定義しなきゃいけなくて面倒という話でした。

でもまぁ私のケースだと多くの場合「複数保持できるが基本1つ」だったりするのでその辺で最適化はできそうなんですね。今日見たProfileだとGC Allocがいっぱいでした。そうだね。

憶測だけで面倒事に自分から突っ込んでいくのは愚かだと思うので色々測って見ようとは思います。少なくとも現状はちょっと重いのよね。


うーん。もうちょっとProfile丁寧にみれたらなぁ。コンパイルされてる時点でアレだけど…。別のUdonへの関数呼び出しとかが消えちゃってて本当にわからないんですよね。

まぁやりようはあるのでやります…。

それもちょっとやりつつ、空をやっていきましょうかね。

7月です。頑張らなきゃね。

おやすみなさい。