ページ

2009年8月28日金曜日

[Silverlight] Popfly Game Engine のソースコード公開

2009年 8月 24日をもって終了してしまった Popfly ですが、その一部のソースコードが CodePlex にて公開されたそうです。
http://popflygameengine.codeplex.com/

Popfly Parting Present」 によると、Microsoft 内の Web サービスと密に結びついていたり、他のプロダクトからのコードを含んでいたり、MS データセンター無しでは使いようが無かったり、といろいろな理由で Popfly の大部分のソースコードは公開することが不可能とのことです。
公開された Popfly Game Engine はゲームデータファイルを読み込んで実行する部分とのこと。
クリエータ (ゲームデータファイルを作る機能) は含んでませんが、データファイルを作るのに参考となる情報は十分に含まれているそうです。
また、サーバと通信するような機能 (ハイスコアやバッジといったものをサーバに保存するような機能) に関する部分も含まれていないとのこと。
しかし、Silverlight, C# でシンプルなゲームエンジンを作る方法を示したコードとしておもしろいものであると思っているとのことです。


[Silverlight][HTML5] uuCanvas.js - HTML 5 の <canvas> を Silverlight で実装

昨日の 「[Silverlight][HTML5] HTML 5 の <canvas> を Silverlight で実装してみたって。。。その発想は無かった」 にコメントを頂きました。

nitoyon さんより

日本でも同じようなことを試してるかたがいらっしゃいます!
http://d.hatena.ne.jp/uupaa/20081114/1226596120

こりゃすごい。
この記事にある 「Silverlight による HTML5::Canvas の実装」 は uuCanvas.js として↓にまとめられています。

http://uupaa-js-spinoff.googlecode.com/svn/trunk/uuCanvas.js/README.htm
uuCanvas.js は HTML 5 の <canvas> をサポートするための JavaScript ライブラリなんですが、Chrome、Safari、Opera、Firefox ではそれぞれのブラウザが持つ <canvas> を、IE では Silverlight で <canvas> 相当の機能を実現するようになっています。(IE で Silverlight が入っていない場合は VML を使うようになってるそうです)
なので、ちょっとしたお約束を守っておけば同じ HTML で IE でもそれ以外でも <canvas> を使うことができます。
詳しいことは uuCanvas.js のサイトを。
また、いろいろなデモもあります。

昨日紹介した記事にも “Aside: Yes, I know I'm not the first person to add <canvas> support to IE. :)” なんてありましたから、いろんな人がいろんな方法で <canvas> を実装したりしてるんでしょうね。
私は、昨日の記事を書くときに 「へぇ、<canvas> ってこういう機能なのかぁ」 と初めて <canvas> のことを知った (それまでは名前しか知らなかった) ような人なのでまったく知りませんでした。


2009年8月27日木曜日

[Silverlight][HTML5] HTML 5 の &lt;canvas&gt; を Silverlight で実装してみたって。。。その発想は無かった

Using one platform to build another [HTML 5's canvas tag implemented using Silverlight!] より。
Microsoft の Silverlight と WPF のデベロッパの方のブログですが、HTML 5 の <canvas> を Silverlight で実装してみたそうです。
いやぁ、すごい。
つか、その発想は無かったな。

記事にはいろいろと <canvas> を表示させてみたスクリーンキャプチャが貼り付けられています。
ただ、さすがにネット上のページを自由に表示できるというわけではなく、自分で HTML を画いてる必要があります。
まずは、<head> の中あたりにでも

<script type="text/javascript" src="Html5Canvas.js"></script>

と追加してやります。
そして、たとえば、

<canvas id="canvas1" width="150" height="150"></canvas>
<script type="text/javascript">
function draw() {
var canvas = document.getElementById('canvas1');
:
}
</script>

という風に <canvas> が使われていた場合は、<canvas>~</canvas> を

<script type="text/javascript">
InsertCanvasObject("canvas1", 150, 150, draw);
</script>

と置き換えてやります。
<canvas> を操作するための JavaScript たち (上記の draw() 関数など) はそのままで OK です。
これで InsertCanvasObject のところに Silverlight アプリが作られて、その中から draw() 関数が呼び出されます。

また、<canvas> の仕様をすべて実装したというわけではないそうです。
Mozilla のサンプルページの最初の 5ページを動かすのに十分な分だけサポートされているそうです。(たぶん Mozilla Developer Center's Canvas tutorial のことだと思う)
また、記事の最初の方に

  • Paths and shapes (move/line/curve/arc/clipping/etc.)
  • Strokes and fills (using solid colors/gradients/images/etc.)
  • Images
  • Context save/restore
  • Transformations (scale/rotate/translate/etc.)
  • Compositing (alpha/blending/etc.)
  • Text (font/alignment/measure/etc.)
  • Pixel-level manipulation
  • Shadows

というリストがありますが、これのイタリックになっているものは何も実装していないそうです。

ソースコードは記事の下の方にある "[Please click here to download the complete source code to Html5Canvas and the sample application shown above.]” というリンクからダウンロードできます。
Html5Canvas.Web プロジェクトを 「スタートアッププロジェクトに設定」 して、TestPage.html を 「スタートページに設定」 して実行すればサンプルページが開きます。
ちなみにソースは Ms-PL とのこと。

ちょこっとだけソースを見てみました。
CanvasRenderingContext2D.cs が処理のほとんどを行ってるんですが、思った以上にシンプルです。
というか、<canvas> の fillRect は XAML の Rectangle に、drawImage は Image に、Path は PathGeometry に、というようにほぼ単に XAML に置き換えてるだけなんですね。
記事にも <canvas> のことを調べてたら Silverlight でネイティブにサポートされているのと同じものが多いと思ったというようなことが書いてありますが、ほんとにそうなんですね。


[Silverlight] Parallel で最適化してみる例

Silverlight 3 のサンプルを Parallel で最適化してみたという例がなかなかおもしろかったので紹介。

もともとは 「Flirting With Silverlight」 にある Silverlight 3 のサンプルです。
(「ActionScript に比べてどうよ?」 みたいなところから始まっているようですが、そのあたりはここでは割愛)
”Example” が Silverlight 3 アプリへのリンクになってます。
見てもらえばそのままですが、これは WriteableBitmap にパーティクル(点々)を描いてみるというサンプルです。
マウスを乗っけるとグリグリと動きます。

このサンプルを Parallel を使って並列化してみたというのが 「Adding Concurrency Optimization in Silverlight 3」 です。
並列化と言ってもそんなに難しいことはしていません。
まず、.NET Framework 4.0 に追加される予定の Parallel.For をまねて ParallelFor メソッドを作ります。
この ParallelFor メソッドは記事に丸ままコードが載っています。
次に、OnStoryboardCompleted メソッドの中の 2ヶ所のループをこの ParallelFor を使うように変更します。
変更結果も記事に載ってますので、ここでは要点だけ。

まずは、

while (--index > -1) 
bitmap.Pixels[index] = 0x000000;

というループ。
これは WriteableBitmap の全ピクセルを 0x0 で埋める、すなわち、真っ黒に塗りつぶすためのコードです。
これを単に ParallelFor を使ったループに変更します。

もうひとつは

while (null != particle)
{
x = particle.X;
y = particle.Y;
z = particle.Z;
:
}

というループ。
こちらは ParallelFor できるように以下の修正を加えたそうです。

  • w、xi、yi といったループの外で宣言されているけど、実はループの中でしか使っていないというローカル変数をループの中に移動。
  • 元のコードでは particle という線形リストになっていたものをあらかじめ配列に格納しておいてそちらを参照するように変更。

コードを見比べてもらえばわかりますが、並列化できるようにしたって言うだけでそんなに大した変更ではありません。

さて、結果です。
OnStoryboardCompleted メソッドは 1フレーム描画するたびに呼ばれるようですから、この 2ヶ所を並列化しただけでもそれなりに効果があります。
記事前半にある “Silverlight Version, optimized to leverage Concurrency” が Silverlight アプリへのリンクになってます。
このアプリではスライダでいくつのスレッドで並列化するのか指定することができるようになってます。
私の環境だとスレッド数 1 だと 34fps くらい、スレッド数 8 だと 89fps くらいでした。
Core i7 で論理コア数 8 なんですが、スレッド数を 8 にした状態でタスクマネージャで見ていると、きちんとすべてのコアが動いてます。
けど、スレッド数をそれ以上に増やすとなぜか fps も下がって、しかも寝ているコアが出てきます。
うーん、なんかコアをうまく使えてないような。

ということで、スレッド数を多くした時の挙動はアレですが、論理コア数と同じスレッド数にしているときにはかなりのパフォーマンス向上が見込めそうです。
もちろん、論理コアがもともと 1しかないような場合にはオーバーヘッドの分だけパフォーマンス低下があるかもしれませんが。

ちなみに、ソースコードは記事の最後の方にある “Strange Attractor Project” というリンクからダウンロードできます。
大元の記事のコードでは StoryBoard で OnStoryboardCompleted を呼び出すというやり方でしたが、OnStoryboardCompleted メソッドが HandleRendering と改名され、CompositionTarget.Rendering イベントで HandleRendering を呼び出すというように変更されています。
StoryBoard だと最大 fps で動かせないでしょうからね。


2009年8月24日月曜日

[VS2010] Visual Studio 2010 のスタートスクリーンをカスタマイズする

Customizing the Visual Studio 2010 Start screen より。
Visual Studio 2010 のスタートスクリーンをカスタマイズする方法が紹介されています。
スタートスクリーンも XAML で書かれているそうです。

(一部のみ抜粋して翻訳)

試すには、まず 「Documents\Visual Studio 10」 フォルダの下にでも 「StartPages」 フォルダを作る。
そして、「Microsoft Visual Studio 10.0\Common7\IDE\StartPages」 フォルダの内容をコピーする。
VS2010 でプロジェクトを開いて、後は自由に編集!

スタートスクリーンのルートは 3つの行の Grid になっている。最初の行は Visual Studio のロゴを、3つ目の行は RSS フィードコントロールを含んでいる。すべてのアクションは真ん中の行にある。注意:真ん中の行はカスタムの VS コントロールを含んでいる。これは次のベータでまたカスタマイズされて置き換えられるだろう。

で、Border の Background を変えて背景色を変えたり、リンクを追加してみたりといった変更例が載っています。
あれ?変更した後にどうすればいいのか書いてないんですが、単に 「Microsoft Visual Studio 10.0\Common7\IDE\StartPages」 フォルダに XAML ファイルを放り込んでやればいいのかな?
# あいかわらず VS2010 を見たこともないので自分では確認できないです

さぁ、これで、きっと誰かがスタートスクリーンを痛IDE化してくれるはずw


2009年8月20日木曜日

[C#] Spec# が CodePlex に登場

Microsoft Research にあった Spec# が CodePlex に登場してました。
http://specsharp.codeplex.com/
と言っても、チュートリアル は 「もうすぐ」 と書いてあるだけですし、ドキュメントは Microsoft Research の方を見てくれ、とかそんな感じなんですが。

Spec# というのは C# に契約指向? (contract oriented) な機能を追加したものです。
Eiffel に 「表明」 と呼ばれる機能がありますが、あんなやつです。
Generics みたいに Microsoft Research で生まれて、その後本家 .NET Framework に取り込まれたものもありますが、Spec# が今後どうなっていくのかはわかりません。
F# も Microsoft Research 生まれですね。

以下、Spec# のわかりやすいところを紹介してみます。

■ NonNull

public Method(object! o)
{
...
}

こんな風に引数の型名に ! を付けると 「null であってはならない」 ことを表明できます。

■ PreConditions

public void Method(int a)
requires a > 0;
{
...
}

こんな風に requires でメソッド開始前の必要条件を指定できます。
「requires ValidString(s)」 みたいにチェック用メソッドを自分で定義してチェックさせることもできるようです。
で、条件を満たさない場合は RequiresException が発生する、のかな?(PPT にそう書いてありました)

■ PostConditions

public int Method(int x, int y)
ensures result == x + y;
{
return x + y;
}

こんな風に ensures でメソッド完了後の必要条件を指定できます。
上記の例では result キーワードを使って戻り値をチェックしてますが、他にもプロパティの値をチェックしたりといったことも書けるようです。
というか、requires と ensures には boolean を返す式をなんでも書けるんじゃないかと思います。

■ その他
Program Verification Using the Spec# Programming System [PPT] なんかを見ると他にもいろんな機能があるようです。
上記の例はいずれもメソッド単位のチェックですが、もっと細かくループ単位でのチェックだとか、反対にクラスのインスタンス単位でのチェックなんかもできるみたいです。


2009年8月18日火曜日

2009年8月7日金曜日

[Silverlight] WriteableBitmap のパフォーマンス

Silverlight 3 WriteableBitmap Performance Follow-Up より。
Silverlight 3 の WriteableBitmap のパフォーマンスが比較されています。
WriteableBitmap っていうのは、結局のところビットマップをバイト配列としてごにょごにょするわけですが、同じようなことができるようになっているクラスライブラリがいくつか公開されています。
それらのパフォーマンス比較です。
比較されているのは、

  1. Silverlight 3 の WriteableBitmap。
  2. オープンソースのゲームエンジン Balder の RawPngBufferStream クラス。
  3. Nikola さんの PngEncoder (Joe Stegman さんの改造バージョン)。
  4. Ian Griffiths さんの SlDynamicBitmap ライブラリ。
  5. Quakelight で使われている 8 ビット BitmapData。
  6. Silverlight 3 のピクセルセルシェーダー。

です。

記事に比較した結果も載ってますが、比較するための Silverlight アプリも貼りつけてありますので、実際に実行して自分の環境でどういう結果になるか試してみることもできます。
ちなみに、ソースもあります。

で、結果です。
1~5 では WriteableBitmap がもっとも速いそうです。
まぁ、ランタイムに組み込まれてるんですから当然という気はしますね。
5 の Quakelight は WriteableBitmap に近い速度が出るそうですが、これは 256色カラーのみに限定しているのでそもそも扱ってるデータ量が違いますからね。
そして、特筆すべきは 6 のピクセルシェーダーです。WriteableBitmap の 10倍の速度が出ています。
ピクセルシェーダーと言うと、既存の画像を加工するものというイメージがありましたが、今回のように計算によって描画できるものであればピクセルシェーダーだけで絵を描いてしまうこともできるわけですね。

記事にはこうあります。
「ピクセルシェーダーは GPU で実行されているわけではないけれども、他のものと比較するとかなり速い。プロシージャルな画像 (計算によって描ける画像) ならばピクセルシェーダーを使うのがお勧め。ただし、Silverlight 3 では制限付きのシェーダーモデル 2 がサポートされているのみということは忘れないように。あと、Silverlight 3 のピクセルシェーダーはマルチコア CPU だと自動的に並列実行されることに気付いた。デュアルコアのマシンだとシングルコアのマシンの 2倍くらいのフレームレートが出ていた。Silverlight でのソフトウエアシェーダーの実装が並列化されているのはまったく正しいし、特別なことではない。シェーダーは GPU 上では並列に実行されるようにデザインされている。」

Silverlight 3 のピクセルシェーダーは GPU で動いているわけではないっていう話だったのになぜ速いんだろうと不思議だったんですが、この自動的に並列化されているっていうのがミソなのかな?
もともとシェーダーランゲージは GPU の中で並列実行されるのが前提ですし。
もしかしたら、WriteableBitmap へのアクセスをうまく並列化してやればピクセルシェーダーを超えるパフォーマンスを出せるかもしれませんね。


2009年8月6日木曜日

[Silverlight] GPU アクセラレーションの RenderAtScale

さっきの記事 「[Silverlight] Re: Smooth:ハードウェア・アクセラレーターの動作確認」 をポストしたあとに RenderAtScale なんていうすばらしいものがあることに気付きました。

さっきの記事でも参考にさせてもらった András Velvárt さんの 「Discovering Silverlight 3 ? Deep Dive into GPU Acceleration」 の後半の “Render At Scale” より。

ざっと訳してみます。
「Silverlight はベクタベースのグラフィックだけど、ビットマップキャッシングはビットマップベース。ビットマップとベクタグラフィックの主な違いは、ベクタグラフィックは品質を落とさずにほとんど無限に拡大できるってところ。一方、ビットマップは大きく拡大するとカクカクになっちゃう。こういったとき、GPU はバイリニアフィルタリングを適用してくれる。[訳注: ジャギーを減らすためにアンチエイリアスしてくれるってことですね]

カクカクがイヤな場合、BitmapCache の RenderAtScale をセットすることができる。

(コードで書く場合)
var bmc = new BitmapCache();
bmc.RenderAtScale = 4;
lion1.CacheMode = bmc;
(XAML で書く場合)
<Canvas>
    <Canvas.CacheMode>
        <BitmapCache RenderAtScale="4" />
    </Canvas.CacheMode>
</Canvas>

上のコードは Silverlight にライオンのベクタを 4倍のサイズでレンダリングするように指示している。ただし、これは 16倍の GPU メモリを要求することになり、キャッシュされたビットマップをレンダリングするのも遅くなる。RenderAtScale の効果は、サンプルの “GPU Acceleration” チェックボックスをオンにしておいて、”High Res Bitmap” をオンにすれば見ることができるよ。」

どうでしょ?
効果のほどはサンプルを見ると一目瞭然だと思います。
András Velvárt さんの 「Discovering Silverlight 3 ? Deep Dive into GPU Acceleration」 の下の方にデモへのリンクがあります。
最初は GPU アクセラレーションを使っていない状態。
これだと、ベクタグラフィックなので拡大されてもライオンちゃんはスムースです。

”GPU Acceleration” をオンにすれば GPU アクセラレーションが有効になった状態になります。
この状態だと、ライオンちゃんが拡大されているときはジャギジャギになります。
これは XAML に書かれたサイズのまま GPU によってビットマップキャッシュされて、それが拡大縮小表示されているためです。

ここで “High res bitmap” をオン。
すると拡大されているライオンちゃんもスムースになります。
同時にフレームレートのところに表示されている 2番目の GPU メモリの消費量が増えているはずです。
これが RenderAtScale=”4” の状態です。
すなわち、XAML に書かれたサイズの 4倍 (面積で 16倍) の大きさでビットマップキャッシュされて、それが拡大縮小表示されているわけです。

GPU アクセラレーションを使い、拡大表示も行うときは、RenderAtScale は重要なパラメータになりそうですね。


[Silverlight] Re: Smooth:ハードウェア・アクセラレーターの動作確認

シーラカンスさんの 「Smooth:ハードウェア・アクセラレーターの動作確認」 を見て私も初めて知ったこと。

まず、フレームレートを表示する
<param name="enableFramerateCounter" value="True" />
について。
これを True にしておくと左上に数字が 4つ表示されます。
Discovering Silverlight 3 ? Deep Dive into GPU Acceleration」 によると、左から

  1. フレームレート
  2. 使用されている GPU メモリのサイズ(キロバイト)
  3. GPU アクセラレートされているサーフェスの合計数
  4. GPU アクセラレートが明示的に指定されていないけど GPU アクセラレートされているサーフェスの数(これについては以下参照)

だそうです。

そして、次に
<param name="EnableCacheVisualization" value="True" />
について。
これを True にしておくと GPU アクセラレートされているところは普通の色で、されていないところは赤色で表示されます。
さらに加えて緑色で表示されるところもあります。
この緑色が何かなんですが、こちらも 「Discovering Silverlight 3 ? Deep Dive into GPU Acceleration」 によると、

Cache visualization shows cached objects in their natural color, and non-cached ones in red. Note that the control panel is above the lions. Therefore it also has to be cached on the GPU otherwise the GPU would not be able to blend it over the lions, and therefore the entire scene could not take advantage of GPU acceleration. We have not marked the control panel to be BitmapCached, so Silverlight has done this for us automatically. The control panel is thus an implicit surface, and displayed in green on the Cache Visualization.


(ざっとした訳)
Cache Visualization はキャッシュされているオブジェクトは普通の色で、キャッシュされていないものは赤で表示される。ライオンの上のコントロールパネルに注目。これも GPU でキャッシュされている。でないと、GPU はライオンの上にこれをブレンドできない。そうすると、シーン全体の GPU アクセラレーションのアドバンテージが無くなってしまう。コントロールパネルの部分には BitmapCache を指定していないけれども、Silverlight は自動的に BitmapCache を指定されていることにしてくれる。と言うわけで、コントロールパネルの部分は暗黙的なサーフェスということになり、Cache Visualization では緑色で表示される。

ということだそうです。
へぇ、良くできてるなぁ。
そして、enableFramerateCounter で表示される 4番目の数字がこの 「暗黙的に GPU アクセラレートされているサーフェスの数」 ということになるわけですね。

シーラカンスさんの記事 「11.ハードウエア・アクセラレーションの効果を見てみる」 のサンプルでも緑色になっているところがあります。
ただ、これらは BitmapCache が指定されているサーフェスと重なっているというわけではありません。
まったく試してもいないあてずっぽうな推測ですが、Button の方は BlurEffect が指定されているために自動的に GPU アクセラレートされているのかもしれません。
けど、Rectangle の方はどういう理由なのかよくわかりませんね。
暗黙的に GPU アクセラレートされる条件ってどっかに公開されてるのかな?


2009年8月4日火曜日

[Silverlight] seadragon.com のクライアント機能

昨日の 「[Silverlight] seadragon.com」 に Kei1 さんからコメントをもらいました。(ありがとうございます)
なかなかおもしろかったので記事にしてみました。

Kei1 さんに教えてもらった
Seadragon.com release!
によると、seadragon.com は Silverlight がインストールされていない場合は Seadragon Ajax が、Silverlight がインストールされている場合は Silverlight が使われるようになっているそうです。
もちろん、Silverlight がサポートされていないブラウザ / OS の場合も Seadragon Ajax が使われます。
Seadragon には iPhone 用の Seadragon Mobile もありますが、それについては何も書かれてませんね。

さっそく IE の 「アドオンの管理」 で 「Microsoft Silverlight」 を無効にしてアクセスしてみました。
Silverlight に比べるとカクつきますがそれなりにきちんと表示されます。
直リンクだけではなく、貼り付けてある場合もきちんと Silverlight の有無を判断してくれます。


2009年8月3日月曜日

[XNA][Silverlight] SilverSprite を使って XNA スターターキットの &ldquo;Platformer&rdquo; を Silverlight に移植してみた、そうです

Porting XNA starter kit "Platformer” to Silverlight (SilverSprite) より。
最初は XNA も SilverSprite もほとんどやったことなかったそうですが、XNA Game Studio 3.1 をインストールしてちょっとしたゲームを作ったりして試した後、XNA のスターターキットに入っている “Platformer” というサンプルゲームを SilverSprite を使って Silverlight で動くようにしてみたそうです。
そしたら、4時間くらいでできちゃったとのこと。

記事には実際に Silverlight 3 で動く Platformer へのリンクもあります。
(XNA 版と同じ構成なので 9Mバイトあるそうです。そのため読み込みにはちょっと時間がかかります)

また、ソースコードも置いてあります。
変更したところは “laumania” で検索すればわかるようになってるそうです。


[C#] C# 4.0 の covariance と contravariance

あいかわらず、Visual Studio 2010 をダウンロードすらしていない私ですが、、、

C# 4.0 : Co-variance and Contra-variance より。
こんなことできるようになるのか。

というか、できなかったんだっけ?
C# 2.0 で delegate の covariance と contravariance はサポートされたけど、ジェネリックな delegate の場合は厳密に型付けされちゃうんだっけ?
それが out, in の記法によって covariance、contravariance を明示的に指定できるようになるってこと?

なんか、もう、どのバージョンで何ができて何ができなかったのか良くわからなくなってきてるな(笑)


[Silverlight] seadragon.com

http://seadragon.com/ がオープンしたそうです。

と言いつつ、Seadragon ってなんだっけ?w
Seadragon の本拠地は http://livelabs.com/seadragon/ だと思うけど、DeepZoom がテクノロジーの名称で、Seadragon がアプリケーションの名称ってことなのかな?
DeepZoom を iPhone で表示するアプリの名前も Seadragon だったし。
で、今回の seadragon.com は DeepZoom な画像をホストしてくれるサイトってこと?

探したらあった。
Seadragon - Deep Zoom on Demand
「Microsoft LiveLabs は Seadragon.com のリリースをアナウンスした。これは Azure ベースのサービスで、DeepZoom 画像のホストをオンデマンドで作成することができ、超ハイレゾ画像のクイックリンクをものすごく簡単に作ることができる」
とあるので、どうやらそういうことみたい。

というわけで、やってみた。
http://seadragon.com/ で画像の URL を入れて Create ボタンを押すだけ。
試しに http://seadragon.com/create/ に例として載ってる画像で Create してみた。
すると、その画像の DeepZoom 版へのリンク、貼り付け用のタグ、Delicious・Digg・Facebook・Twitter へのポスト用のリンクが出てくる。
貼り付け用のタグを貼りつけてみたのが↓

ちなみに、画像の URL が同じ場合は毎回同じ結果になるようです。