RubyCocoa で QuartzComposer CustomPatch をつくりたい
先に今の状態 : できそうな糸口がつかめたところで挫折 :(
SocketReaderPatch、ApacheLogPatch とQuartzComposerのカスタムパッチをつくってきたのですが、ここらでRubyで書くかと思い立ちました。 バベル案内 と ホワイの(感動的)Rubyガイド を読んで、こんなにもRubyが世界に受け入れらているのかと感動したことと、いくつかCustomPatchをつくってみて、CustomPatchj作成の概観がつかめてきたから。
どうやるか
RubyCocoaのsvn trunkを使います。 最近のリビジョンでは、QuartzComposer.frameworkが使えるようになっているのです。
>> require 'osx/cocoa' r=> true >> OSX.require_framework 'QuartzComposer' => true >> OSX::QCStructure => OSX::QCStructure >> OSX::QCPatch => OSX::QCPatch
ひゃっほう。
で、いつもどおりQuartzComposer CustomPatch の Xcode template をつかって、Xcodeプロジェクトをはじめます。
RubyCocoa.frameworkをリンクしておき、RubyPatchPrincipla.m を
#import <rubyCocoa/RubyCocoa.h> #imporut "RubyPatchPrincipal.h" @implementation RubyPatchPlugin + (void)registerNodesWithManager:(GFNodeManager*)fp8 { static bool loaded = false; if (!loaded) { if (RBBundleInit("/tmp/custom_patch.rb", self, nil)) { NSLog(@"[RubyPatchPlugin.registerNodesWithManager] RBBundleInit failed"); } else { loaded = true; } } Class helperClass = NSClassFromString(@"RubyPatch"); [fp8 registerNodeWithClass:helperClass]; } @end
のように書きました。 RubyCocoaのbundle生成用APIを用いています。
loadされるRubyスクリプトはこういうもの。
require 'osx/cocoa' OSX.require_framework 'QuartzComposer' class RubyPatch < OSX::QCPatch #attr_accessor :inputFoo, :outputBar def RubyPatch.logger=(l) @@logger = l end def RubyPatch.executionMode @@logger.info("executionMode") 2 end def RubyPatch.allowsSubpatches @@logger.info("allowsSubpatches") 0 # in objc.h, NO is defined as 0 end def RubyPatch.timeMode @@logger.info("timeMode") 1 end def init @@logger.info("init") @inputFoo = OSX::QCBooleanPort.alloc.init end def initWithIdentifier(fp8) @@logger.info("initWithIdentifier") init self end def setup(fp8) @@logger.info("setup %s, %s", fp8, fp8.class) fp8 end def cleanup(fp8) @@logger.info("cleanup") end def enable(fp8) @@logger.info("enable") end def disable(fp8) @@logger.info("disable") end def execute(fp8, time, arg) @@logger.info("execute") true; end end OSX.init_for_bundle do |bundle, param, logger| logger.info("init bundle=%s param=%s", bundle, param) RubyPatch.logger = logger end
templateが生成するObjective-Cのコードを、Rubyに書き下したものですね。
実行
ビルドもすんなり通り、さっそく実行してみます。
アウチ。 なんか例外が投げられてしまいます。 出しているログを眺めてみると…
正常なもの (Objective-Cで書いたカスタムパッチ)
2007-07-14 14:35:03.568 Quartz Composer[15580] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle (loaded) param= 2007-07-14 14:35:03.708 Quartz Composer[15580] initWithIdentifier 2007-07-14 14:35:03.708 Quartz Composer[15580] executionMode 2007-07-14 14:35:03.708 Quartz Composer[15580] timeMode 2007-07-14 14:35:03.710 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.785 Quartz Composer[15580] setup
だめなもの (RubyCocoaで書いた)
2007-07-14 14:36:11.300 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle (loaded) param= 2007-07-14 14:36:11.301 Quartz Composer[15602] assert passed ! 2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): initWithIdentifier 2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init 2007-07-14 14:36:11.345 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches 2007-07-14 14:36:11.377 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches 2007-07-14 14:36:11.378 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches
よく見てみると、RubyCocoaで書いた方は、initWithIdentifier のあとに executionMode, timeMode が呼ばれることなく allowsSubpatches が呼ばれています。 executionMode, timeMode が分からないので、QuartzComposer としてはこのパッチをどの種類にして良いか分からず、レンダリングできませんよーという例外がきてるっぽい。
問題
ではなぜ executionMode, timeMode が呼ばれないか、考えてみます。
QCPatchのヘッダによると、executionMode, timeMode はそれぞれ int を返すメソッドのようです。 ところが、RubyCocoaで用意した executionMode, timeMode は返り値の型を指定していません。 ちょっと調べたところ、RubyCocoaではRubyのメソッドが整数型を返すとき、Objective-Cの世界にはNSDecimalNumberの型を返すようになっているようです。
その結果、executionMode, timeMode は別のメソッドとして認識されてしまい、QCPatchのメソッドをオーバーライドできず、呼ばれていないのではないかと推測しました。
解決案
- intで返すようにRubyCocoaでなんとかする
- QCPatchをRubyクラスで継承せずにObjective-Cのクラスで継承させ、そのメンバにRubyクラスのdelegateのオブジェクトをもたせて、実際の処理はdelegateオブジェクトに振る
2が手っ取り早そうですが、いまひとつかっこいくないなぁ。
別の問題
CustomPatchのテンプレートによると、QCPatchを継承したクラスのメンバに QCPortクラスのインスタンス (ex. inputEnable, outputString など) を書くことで、パッチのinput/outputポートを指定できるような仕組みになっています。
じゃあ、
attr_accessor :inputEnable, :outputString ...
みたいにすればいいのか、というとどうもそうでもないみたいで…
やはり解決案2を使うしかないのかもです。
- Newer: RubyCocoa で QuartzComposer CustomPatch (2)
- Older: TopCoder::SRM::357
Comments:0
Trackbacks:0
- Trackback URL for this entry
- http://blog.deadbeaf.org/2007/07/14/make-quartzcomposer-custompatch-with-rubycocoa/trackback/
- Listed below are links to weblogs that reference
- RubyCocoa で QuartzComposer CustomPatch をつくりたい from mootoh.log

