読者です 読者をやめる 読者になる 読者になる

Swiftのジェネリクスで型引数を別の型引数の制約に利用するとCommand failed due to signal: Segmentation fault: 11 が解決していた

別に大したこだわりはないんですけど,以下のようなことができなくてあれ?と思っていたところ blog.a-azarashi.jp

Xcode 8.3 , Swift 3.1 にアップデートしたお陰か,ビルドが通るようになっていました。

class MVC<M: Model, V: View, C: Controller<M, V>

これができるということです。ええやん。

BMSプレイヤー開発 for macOS: サウンドシステム編

前回の記事でBMSパーサー実装時の動作テストとして,パース結果をもとに譜面(の音声)をオートプレイできるところまでいけたと書きましたが,どうにも音声が遅延する問題が気がかりでした。 今回はその問題が解決できたことを報告いたします。

AVAudioPlayerはゲーム向きでない

結論です。聞いていてわかるほどの遅延が発生してもはやゲームになりません。音ゲーじゃなかったらギリギリ許せるレベルですが,できるならゲームへの採用は避けた方が良いということがわかりました。SpriteKitにもSKAction を使った音声の再生手段を提供しているようですが,自由度がないためキー音を大量に扱う音ゲーに於いてはAVAudioPlayer同様不安要素があります。

CoreAudioの概要という公式の資料を読んでみた

CoreAudioの概要 知りたい情報はわりかし序盤の方に出てくるのですが,内容が興味深くて最後まで読んでしまいました。特にDTMをやっている人には AudioUnit の概念が割と突っ込まれて書かれいるので興味を引く内容になっているのではないのでしょうか!読み物としてオススメです。

公式の見解では,ゲーム開発においては OpenAL のAPIを利用が適している,のだそうです。 OpenGLライクなAPIで音声を扱えるAPI群だそうで,広く利用されているらしいです。これがCoreAudioの上位レイヤー部分に実装されており,このAPIを利用すると遅延の少ない音声再生を実現できるとのことでした。

OpenALについての資料をあさる

参考にしたページは以下

developer.apple.com

github.com

コードを読むことが一番の近道です。

公式が提供しているObjective-Cの実装は音声を再生する手順だけではなく,パンを変えたりリバーブ等のエフェクトを掛けるところまで解説しています。IIDXっぽくいくなら音声に対するエフェクトの適用は必須なのでありがたい限りです。

またgithubに公開されていたswift-openal-example はswiftの特徴を生かし,日本語のメソッドでわかりやすくAPIの利用フローが書かれており,素早い理解への一助となりました。

どちらもmacOS及びiOSにおけるOpenALの理解への近道なのでオススメです。音声を単に再生するだけが目的なので上に紹介した実装を見れば十分であり,今回は公式のドキュメントにはあまり目を通しておりません…。おかげでメモリリークなどの問題に若干悩まされたりしまいたが…–;

ALUREでoggが再生できるとのこと

OpenALを便利に利用できるライブラリとして ALURE というものがあることを知りました。OpenALをより便利に使える OpenGLにおける GLUT 的なライブラリです。

公式サイトには

Currently ALURE includes a basic .wav and .aif file reader, and can leverage external libraries such as libSndFile (for extended wave formats and several others), VorbisFile (for Ogg Vorbis), FLAC (for FLAC and Ogg FLAC), and others

と書かれており,.ogg の読み込みをサポートしてしまうとのこと。

ALURE(OpenALユーティリティ)をiOSで使う をもとに,プロジェクトに取り込んでみると,簡単に .ogg を再生することができました。拍子抜けです。

他の方法としては oggのデコーダと Audio Queue Servicesを利用することで実現する方法があるそうです。 IDZAQAudioPlayer このリポジトリをビルドするとiOSで .ogg を再生できることを確認できました。 今回はOpenALを利用しているので,こちらは不採用となりましたが,参考までに。

かくして遅延はなくなった

厳密には遅延しているのでしょうが,聞いていてい不自然になるような出力結果にはなっていません。ひとまず安心できるレベルです。新しい課題として,キー音でないバックで流れる音声は処理落ちすると簡単にずれてしまうので,譜面の進度に合わせて音声の再生位置を無理やり同期させる処理が必要そうということがわりました。それは追々やっていきます。

詳しい実装に関して

あまりなさそうな情報なので,Qiitaに書いておきました。

qiita.com

BMSプレイヤー開発 for macOS: BMSパーサー編

開発近況

harpこと BMSプレイヤーfor macOS の開発ですが,忙しさを言い訳にしてしばらく手が止まっていました。 最近ようやく以前のようにモチベーションと時間を取り戻してきたので少しづつ進めています。 今はBMSパーサーを書いているところで,BMSの仕様のおさらいと,なにを取捨するのかをキッチリ決めることから始めました。

BMSの仕様は基本的にLR2に準ずる

BMSの仕様をしらべていくと,ものすごく詳細に歴史的経緯を踏まえた仕様を解説しているページを発見しました。

BMS command memo (JP)

このサイトを信じてLR2が実装しているBMS上の仕様をひとまずは模倣していくところから始めています。 というのもLR2がだいぶメジャーであり,(想像ですが)これを基準に譜面を作成されることも多いだろうし,このLR2の挙動を再現できればほとんどの譜面がサポートできるのではないかと思っているからです。

実装上簡単に追従できそうなものや,逆にこれ要らないんじゃない?というものは一部省いたりしています。

とにかく仕様が膨大で複雑なのでDone is better than perfectな精神で進めていくつもりです。

サポートする仕様はgitのwikiにまとめました。

サポートするBMS仕様 · gomachan7/harp Wiki · GitHub

実装メモ

仕様を眺めるとコマンドの意味的な分類と記述形式が Control flow, Header, Channel message の三種類に別れているので,それぞれこの優先度で単独で1行ごとにパースを試みる処理にかけて,正規表現で引っかかればパース成功として読み込む行を進めるという実装になりそう。

BPMの変更仕様がチャンネルメッセージに直接値を書く チャンネル3 と, ヘッダセクションで定義した BPM値を読みにいく チャンネル8 2つあるのがややこしい。ややこしいだけじゃなくて,内部的にこいつらをマージするのが結構面倒で泥臭かったりします。

あと気をつける点は チャンネル1 の音声再生命令は,同じ小節に命令が複数行に別れて複数定義されていても,マージせずに別物として読んでおく必要があります。普通のノートやその他チャンネルとはちょっとだけ扱い方が違うので注意しないと,なんで音が再生されないだ?と当てもなくデバッグし続けるはめになります(経験済み)

進捗共有

Done 譜面の読み込み,自動再生まで完了。

懸念 oggがデフォルトで読み込めないため,自前でデコードする処理を書いてあげるか事前にない場合はwavにでも変換してあげるプロセスを挟んであげる必要があります。 また,音声再生関連で一番簡単なAPI,AVFoundationのAVAudioPlayerだとだいぶ遅延が生じてしまう問題があるのでCoreAudioの資料を読み進めてどうすればいいのか考え中です。

リモートワークの話

要するにリモートワークがしたいです,ということです。

僕が勤めているのはそこそこの規模のイケイケ系IT会社で,見込み残業が給料に含まれていて残業代は一切でない,よくある裁量労働制を採用している会社なのですが,制度としてリモートワークやフレックスタイム制といったものがありません。所謂出勤時間に厳しく( 最近は弊チームではすごく多めに見てもらっていて個人的に助かっておりますが ),退勤時間が無限解放な,そんな感じの労働環境と言っていいでしょう。裁量労働ならば,こういうリモートやフレックスといった働き方とは相性がいいはずなので,是非ともやってもらいたいと思っています。ですが,現実的には難しいようです。

部署によっては週1でリモートワークを許可しているところもあるらしく,本日人事の方と話していてリモートって難しいんですかねぇ?と投げかけたところ,え?週1でもないの? という反応をされてしまいました。 僕も個人的にリモートしたいだの,せめてフレックスは可能なのかということをちょいちょい訴えてはいるのですが,やはり会社としての制度を変えるのは大変なようでなかなか勤務体制を変える動きまでは至っていません。

うちの会社は人材の動きが激しく,2,3年もすると新卒で入ったエンジニアの同期が結構な人数出て言ってしまうのが恒例となっていますが,こういった働き方をちょっと許容するだけでエンジニアの幸福度が段違いに上るし,人の動き,流れも変わると思うのですが,如何なものなのでしょうかね?個人的な価値観になってしまいますが,留まるに値する十分な理由って,何もお金や組織感だけじゃないと思うんですよ。これから何十年も続けていくのですから,ライフスタイルにまで影響を与えるような働き方が許容されるのは素敵なことだと思っています。

やっぱりMTGは重要だしフルリモートしたいだなんていう贅沢は言いません。対面での会話の重要性は十分わかっているつもりですが,さぁ開発フェーズだ といったときにフロー状態に入れるような環境づくりの選択肢としてリモートはやっぱりありなんじゃないでしょうか?

ですが…,今日エンジニア向けの集中開発専用ルーム的な部屋(弊社では精神とテクの部屋と呼んでいます)で作業していて,壁際の人に気づかれないような席を陣取っている人があからさまに寝ていて,リモートワークを採用したくない側の不安な気持ちもちょっとわかりました。あと僕の出勤時間がよくずれるのも原因か。。説得力無いもんな。。。でも裁量労働だから,やることやっていれば悪いことではないと思っています。

定時退社の話

定時退社するのは自分のためだし,長い目でみると会社のためにもなるので僕は定時退社を続けます。職業柄,どうしても残らなければいけないことがあることは理解した上で,でもあまり無理をしないで長く続けて行きたいです。

ViewControllerの戻るボタンのタップをハンドリングする。(戻る動作をキャンセル可能)

iOSにおいて前の画面に戻る際, 「本当に前の画面に戻りますか?」 というアラートダイアログを出して,選択次第で戻る動作をキャンセルしたい需要が生まれました。あまり大げさな実装はしたくないので,iOSが用意するUINavigationControllerによる画面遷移の仕組みに乗りたいとします。 どう対処すれば良いでしょうか。

qiita.com

こちらで紹介されている方法では,戻る動作を検知する以上のことはできませんが,これから提案する方法は戻る動作自体を取りやめることが可能になります。

なお,デメリットとして UINavigationController を拡張し,それを利用すると言うお約束が必要です。今回紹介する実装はpopする場合にとどまっていますが,modalとして呼び出された際のdismissも同じ理屈でハンドリング可能です。

UINavigationControllerの独自実装

まずは,前提となる独自実装です。UINavigationControllerへViewControllerをpushする際に,pop時のコールバックを明示的に指定するところがキモで,ここで本当にpopしていいかを一度delegateに問い合わせるということをしています。delegateが実装されていない場合は通常どおりpopされるだけです。

@objc protocol NavigationViewControllerDelegate: class {
    @objc optional func navigationController(_ navigationController: MyNavigationController,
                                             willPopViewController: UIViewController) -> Bool
}

final class MyNavigationController: UINavigationController {

    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        let action = #selector(pop)
        viewController.navigationItem.leftBarButtonItem?.action = action

        super.pushViewController(viewController, animated: animated)
    }

    @objc private func pop() {
        guard let willBePoppedVC = viewControllers.last else {
            return
        }

        if let delegate = willBePoppedVC as? NavigationViewControllerDelegate {
            let willBePoppedVC = delegate.navigationController?(self, willPopViewController: willBePoppedVC) ?? true
            if !shouldPop {
                return
            }
        }

        popViewController(animated: true)
    }
}

ハンドリングしたいViewControllerの実装

navigationController(_:willDismissViewController) の返り値のtrue/falseでpopしていいかを MyNavigationController に伝えます。 たとえば shouldShowAttention というbool値を用意しておき,これがfalseのときはダイアログを出さずにそのままpop,trueのときは,一旦popを取りやめて,ダイアログの選択次第であとから明示的にpopするということが可能になります。

final class SampleViewController: UIViewController, NavigationViewControllerDelegate {
    private var shouldShowAttention = false

    // NavigationViewControllerDelegate
    func navigationController(_ navigationController: MyNavigationController,
                              willPopViewController: UIViewController) -> Bool {
        if shouldShowAttention {
            showAttentionDialogBeforeLeaving(okCallback: {[weak self] _ in
                _ = self?.navigationController?.popViewController(animated: true)
            })
        }
        
        return !shouldShowAttention
    }

    private func showAttentionDialogBeforeLeaving(okCallback: @escaping () -> Void) {
        let alertDialog = UIAlertController(title: "警告", message: "本当に戻る?", preferredStyle: .alert)
        let okAction = UIAlertAction(title: "はい", style: .default, handler: { _ in okCallback() })
        let cancelAction = UIAlertAction(title: "いいえ", style: .cancel, handler: nil)
        
        alertDialog.addAction(cancelAction)
        alertDialog.addAction(okAction)
        
        present(alertDialog, animated: true, completion: nil)
    }
}

使い方

普通のUINavigationControllerと一緒です

let navigationVC = MyNavigationController(rootViewController: SampleViewController())
navigationVC.pushViewController(SampleViewController(), animated: false)
show(navigationVC, sender: nil)

以上,ViewControllerの戻る動作を取りやめたい場合があったので,とりあえず思いついた実装を紹介しました。意外と需要があるはずなので,何かのお役に立てれば幸いです。

追記:
バックジェスチャ?っう頭が…

Swiftのジェネリクスで型引数を別の型引数の制約に利用するとCommand failed due to signal: Segmentation fault: 11(未解決)

原因の詳細までは特定できていませんが,該当箇所を修正すると直るのでそういうことだと思っています。Swift3.0です。

製作中のゲームのSKSceneにロジックをずらずら書いていくのはしんどそうなので,自前でMVCの雛形を用意し,SKSceneクラスでやることはイベントをそのままControllerに流すのとMVC保持程度にしたいなと思っています。 そこで,任意のシーンにMVCを持たせる際,MVCの組み合わせにゆるい制約をもたせようと軽いDI的な仕組みを実装してみたいのですが, class MVCContainer<M: BasicModel, V: BasicView, C: BasicController<M, V>> と書いた部分で Segmentation fault が発生してしまうようでした。

XCodeはコンパイル時までエラーも警告も出しません。なぜだろう。同じことをM,V,Cを指定して実現できる方法はあるのでしょうか。そこまでこだわる部分じゃないのですが,釈然としないので原因と解決案を探り中です。

import Foundation
import SpriteKit

class BasicModel {
  required init() {}
}

class BasicView: SKNode {
  required override init() {
    super.init()
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

class BasicController<M: BasicModel, V: BasicView> {
  let model: M
  let view: V

  required init(model: M, view: V) {
    self.model = model
    self.view = view
  }
}

class TestModel: BasicModel {
}

class TestView: BasicView {
}

class TestController: BasicController<TestModel, TestView> {
}

class MVCContainer<M: BasicModel, V: BasicView, C: BasicController<M, V>> {
  let model: M
  let view: V
  let controller: C
  
  init() {
    model = M()
    view = V()
    controller = C(model: model, view: view)
  }
}

class TestScene: BasicScene {
  let mvc = MVCContainer<TestModel, TestView, TestController>()
}