takumifukasawa’s blog

WebGL, Shader, Unity, UE4

【Javascript】素のjsでシングルトンクラスを実装する(ver. 2021/7)

jsでシングルトン的なことを実現する場合、module や import / export を使用できる環境下では例えばこのように export でインスタンスを渡すことでシングルトン的な振る舞いをさせることができます。

module.exports = new Klass();

export default new Klass();

最近、ブラウザ上で実行する素のjsでシングルトン的な機能が欲しい場面があり、クラスを使ったいわゆるシングルトンを作ってみようと思いました。

新しいAPIを試してみたいと思いMDNを漁っていたところ、private class fields が主要ブラウザ(chrome,firefox,safari)の各最新バージョンの「ほとんど」で実装されているみたいだったので使ってみることにしました。

「ほとんど」と書いたのは、 firefoxで private class fields を使うことができるのは v90 からで 2021/7/7時点のfirefox最新版v89系だとまだ使用出来ないためです。ただ、数週間のうちにv90系が出るらしいのと、今回は仕事で使う用途ではないので実験的に組み込んでみる判断にしました。

developer.mozilla.org

class Singleton {
  static #instance;
  num;

  static get instance() {
    if(!Singleton.#instance) {
      throw "no instanced";
    }
    return Singleton.#instance;
  }

  constructor(num = 0) {
    if(Singleton.#instance) {
      return Singleton.#instance;
    }
    Singleton.#instance = this;
    this.num = num;
  }

  up() {
    this.num++;
  }
}

...

const s = new Singleton();
s.up();
s.up();
console.log(s.num); // 2

...

const ss = Singleton.instance;
// ↑ const ss = new Singleton(); でも同じインスタンスが返ってくる

ss.up();
ss.up();
console.log(ss.num); // 4

ちなみに、private class field が # を使って表す理由はこの記事がとてもわかりやすかったです。「# しか使うことが出来なかった」そうです。

blog.jxck.io

【Javascript】ビット演算で2の累乗判定する関数

2の累乗判定をするときに、ビット演算だとかなりシンプルになることを知ったのでそのメモになります。

こちらがその関数です。

const isPowerOfTwo = x => (x & (x - 1)) === 0

原理は以下のようなものです。

数値が2の累乗の場合は必ず最上位ビットのみが1になります。 その数値に1を引くとすべてのビットが反転するので、元の数値-1をした数値論理積 & を使うと 0 になる性質を利用しています。

2 ... (10 & 01) === 0 => 00 === 0 => true
3 ... (11 & 10) === 0 => 10 === 0 => false
4 ... (100 & 011) === 0 => 000 === 0 => true
5 ... (101 & 100) === 0 => 100 === 0 => false
8 ... (1000 & 0111) === 0 => 0000 === 0 => true

【vscode】MacのUnity開発でC#の補完が動かなくなったので対処

MacvscodeでUnity C#の補完が効かなくなったので原因を探りました。

起こっていたこと

環境は、 mac OSbig sur, unity は 2019.4.25f です。

vscode の Omnisharp Log を見てみると、大きく2つのエラーが発生していました。

  • Could not load file or assembly 'Microsoft.CodeAnalysis.Workspaces ...

  • OmniSharp server load timed out. Use the 'omnisharp.projectLoadTimeout' setting to override the default delay (one minute).

どうやら読み込めていないモジュールがあり、読み込みのタイムアウトが起こっているようです。

解決

まず2つ目のエラーに着目して、 omnisharp.projectLoadTimeout の時間をデフォルトの60秒よりも長い設定にしても変わらずでした。

最終的に、vscode の拡張の C# for Visual Studio Code のバージョンを最新の 1.23.12 から 1.23.11 に戻すことで直りました。

f:id:takumifukasawa:20210618235857p:plain

必要な assembly がなかった関係でロードのタイムアウトが起こっていたようです。

【Unity】Timelineで枠外に隠れたカーブの全体を表示するショートカット

現象

Timeline の graph view で key の位置やイージングの調整をしたいとき、下の画像のようにカーブ全体が表示されないという現象が度々発生して困ることが多くありました。

f:id:takumifukasawa:20210309222434p:plain

[Edit] -> [Shortcuts...] を覗いてみても、カーブ全体を表示するようなショートカットはありませんでした。

f:id:takumifukasawa:20210309222458p:plain

解決

F キーを押すことでカーブの全体表示がされるようになります。

FキーはScene View などで特定のオブジェクトにフォーカスする用途で頻繁に使いますが、Timeline のカーブにも同様の処理がされるようです。

f:id:takumifukasawa:20210309222509p:plain

Timeline に限らず、VFX Graph などでも特定のノード・ブロックにフォーカスしたいときはFキーを使うので、どのTabかを問わず「何かが隠れている or 小さく表示されている」->「全体表示する or フォーカスする」という挙動をさせたいときにはひとまずFキーを押してみるのは有用かもしれません。

【Node.js】画像群を一括圧縮するスクリプト

画像圧縮をする際、jpegminiやimagealphaなどのアプリを使ったりPCにインストールしたcli経由で行うことが多かったのですが、これらの方法だと画像圧縮の方法がプロジェクトの各人に依存してしまうという問題があります。 そこで、画像圧縮ツールもnpmで管理する形だと人に依存しない & スクリプトを使い回すことができるなと思ったので、nodeスクリプトを作成してみました。

ディレクトリ指定をするとそのディレクトリ以下を再帰的に、単一ファイル指定だとそのファイルのみを圧縮するようにしました。pngとjpgに対応しています。

圧縮には imagemin を用いました。ファイルにもよりますが、100近いファイルを圧縮したところ全体で3分の1ぐらいまでに減りました。

github.com

サービスがクローズしたjsdoitのコードをgithub-pagesに移行するまで

1年半ほど前にクローズになった、jsdoitというソースコード共有サービスがあります。

サービスが稼働している時は500以上のコードを上げていて、サービスクローズ前に全てのコードをローカルに落として個人のgoogle driveに保管していたのですが、パッと見返したいコードなどがあるため、git管理をしてgithub-pagesで見られるように移行しました。

takumifukasawa.github.io

f:id:takumifukasawa:20210304194024p:plain

ソースコードのOGP対応もさせてみました。

takumifukasawa.github.io

いざ作業に取り掛かると、知らなかったツール・使ってみたいと思っていたツールに触れることができたのでよい機会でした。

せっかくなので、移行までの簡単なフローと、便利だと思ったツールを書いていこうと思います。

jsdoitのファイル群

jsdoitからソースをダウンロードするとhtml,js,cssのファイルが格納されています。 ただ、画像や動画、音声などのアセットは含まれていませんでした。

https://github.com/takumifukasawa/jsdoitArchives/tree/master/src/archives/%5B2016.2.4%5D%20%E3%82%B5%E3%82%AB%E3%83%8A%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%80%8C%E5%A4%9C%E3%81%AE%E8%B8%8A%E3%82%8A%E5%AD%90%E3%80%8D%E3%82%AB%E3%83%90%E3%83%BC%E9%A2%A8

移行フロー

大まかな流れはこちらです。全てnodeのスクリプトを書いています。

https://github.com/takumifukasawa/jsdoitArchives/tree/master/batches

1. ソースコードのパース・変換
  - 画像など切れているリンクの置き換え
  - opg用のmetaタグ挿入
2. サムネイル生成
  - アーカイブ一覧ページのhtml用の画像(465x465)
  - ogp画像(1200x630)
3. アーカイブ一覧ページのhtml生成

使用ツール・サービス

unpkg

リンクの置き換えに関して、jsdoitのサーバーにアップされていたライブラリのリンクは全て置き換えていく必要があるので、 可能な限り洗い出し、正規表現で検知・置き換えをしていきました。

ライブラリに関しては、動くデモを作るために可能な限りコードを書いた当時のバージョンを使っていきたい意図がありました。 主要ライブラリだと何かしらのcdnに上がっているのですが、ライブラリに依存するライブラリは見つからない場合があります。 threejsのOrbitControlsはまさにそのパターンです。バージョンが異なるとエラーが出て動かない場合があります。

unpkgはnpmのバージョンごとにコンテンツを配信してくれているサービスです。 threejsや関連モジュールはunpkgを参照するようにリンクを変換し、各ライブラリのバージョンを揃えることができました。

unpkg.com

node-html-parser

ダウンロードしたhtmlの中にはogp向けのmetaタグは入っていなかったので、ogp用のmetaタグは全て新規追加していく必要がありました。

htmlそのものはテキストファイルなので、htmlファイルを読み込んでもdocument.appendChildなどのnodeベースで要素を追加していくことができないため、どう変換するのがよいか考えながらツールを探した結果こちらが便利でした。

www.npmjs.com

htmlの中身をパースしてquerySelectorを用い要素にアクセスすることができるインターフェースになっている & 文字列への変換メソッドが生えている点が便利で、以下のような形でogp用のmetaタグを追加していきました。

import { parse } from 'node-html-parser';

...

// htmlファイルを読み込む
const htmlContent = fs.readFileSync( ... );

// htmlファイルの内容をパースし、head要素を取得
const root = parse(htmlContent);
const head = root.querySelector("head");

// headの中身を文字列にしてキャッシュ
const headText = head.toString();

// ogp向けのmetaを後続に追加
headText += "<meta ...  />";
...

// headの中身を置き換え
head.set_content(haadText);

// htmlファイルを書き込み
fs.writeFileSync( ... );

puppeteer

画像キャプチャはheadlessブラウザのpuppeteerを使いました。

https://pptr.dev/

公式のサンプルを引用させていただくと以下のようなコードのみで画像キャプチャをしてくれます。 headlessブラウザを使ったことがなかったのでよい機会になりました。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });

  await browser.close();
})();

余談

playwrightも流行っているみたいです。

playwright.dev


9割方は、ひとまず動くところまで機械的に変換することができました。 残りは手動になりそうですが、見つけ次第整えていきたいと思います。

【Javascript】Promiseを直列実行

内容は短めですが、よく使うのでそのメモになります。

複雑なアニメーションなど、とあるPromiseがresolveになったら違うPromiseの状態の監視を始めたい場合が頻繁にあります。

thenで繋いでもよいのですが、Promiseが増えるごとにthenの記述も合わせて増えていくため少し冗長です。

そこで以下のような関数を用意し、Promiseを返す関数の配列をreduceして繋いであげることで、記述量を減らすようにしました。

rejectな場合への対応がいらない場合に、順番に実行していきたい時に多用しています。

async function execPromiseInSequence (arr) {
  return arr.reduce(
    (chained, func) => chained.then(func),
    Promise.resolve()
  );
};

// ex
async function main() {
  // 上から順に実行される
  await execPromiseInSequence([
    async () => { ... },
    async () => { ... }
    ...
  ]);
}