TOP > プログラミング関連 > SFML非公式翻訳トップ > 【非公式翻訳】SFML2.4チュートリアル > システムモジュール > ユーザー定義のストリーム

[原文(公式)]

◆◆ ユーザー定義のストリーム ◆◆
SFML2.4 非公式日本語翻訳
はじめに
SFML にはリソースを扱うクラス群があります。リソースというのは画像、フォント、サウンド、などのことです。
こういったリソースは通常、ファイルから読み込んでプログラム内で使いますよね(つまり、loadFromFile()関数を使う)。
あるいはリソースは実行ファイルに埋め込まれていたり、アーカイブ化されていることもあるかもしれません (つまり、loadFromMemory()関数でメモリから取得)。
大体は、この2つの方法で事足りると思います。が、足りないこともある。

たとえば、圧縮ファイルからリソースを読み込みたいかもしれない。暗号化されているファイルから読み込みたいかもしれない。 ネットワークを通して、他のコンピューターから読み込みたいかもしれない。
そういう特殊なケースのために、SFML には第3の関数があります。loadFromStream()関数です。
この関数は、抽象クラス sf::InputStream からデータを読み込みます。 抽象クラス(インターフェイス)なので、中身は好きなように実装できます。

以下、このページでは、独自のストリームクラスを実装して使う方法を紹介します。
標準のストリーム機能を使えばいいのではなくて?
他の言語と同じように、C++ にも入力ストリーム用のクラスが用意されています。
用意されているクラスは2つ。std::istream と std::streambuf。
前者はフロントエンド専用。独自のデータを扱うための抽象インターフェイスを提供しているのは後者です。

残念ながら、どちらもあんまりユーザーフレンドリーではありません。 しかも、細かい制御をしようとすると、非常に複雑になってしまいます。

Boost の Iostreams ライブラリはシンプルなインターフェイスを実現しようとしているようです、が、 Boost は非常に巨大なライブラリなので、SFMLとしては「使うといいよ」とは言いにくい。

そんなわけで、SFML は、独自のストリーム用のインターフェイスを用意しています。
よりシンプル&高速、で、ありますように……。
実装すべきインターフェイス
sf::InputStream という抽象クラス(インターフェイス)があります。
これが SFML が用意している独自のストリーム用のインターフェイスです。

この内部では4つの仮想関数が宣言されています。

以下は、各関数の仕様です。
このインターフェイスを継承し、仕様に沿って中身を実行することで、独自のストリームクラスとなります。

class InputStream
{
public :

    virtual ~InputStream() {}

    virtual Int64 read(void* data, Int64 size) = 0;

    virtual Int64 seek(Int64 position) = 0;

    virtual Int64 tell() = 0;

    virtual Int64 getSize() = 0;
};
read()関数は、"size" バイトのデータをストリームから取り出し、"data" にコピーします。
戻り値は実際に取り出すことができたデータのバイト数です。エラーのときは -1 となります。

seek()関数は、ストリーム内での現在の読み込み位置を変更します。
引数 "position" が表しているのは、絶対位置です。データの開始位置からの相対座標とも言えます。

tell()関数は、ストリーム内での現在の読み込み位置を返します(単位は byte)。エラーのときは -1 になります。

getSize()関数は、ストリーム内のデータの総バイト数を返します。エラーのときには -1 となります。

FileInputStream と MemoryInputStream
SFML2.3以降、内部でのオーディオデータの管理のために新たに2つのクラスが加わってます。 sf::FileInputStream と sf::MemoryInputStream です。

sf::FileInputStream は リードオンリーでファイルを読み込むストリームのクラスです。
sf::MemoryInputStream は メモリから、リードオンリーで読み込むストリームのクラスです。
どちらも sf::InputStream を継承して作られてます。ということはつまり、オブジェクト指向でおなじみのポリモーフィズムにもとづいた使い方ができます。
自家製ストリームクラスを使う
sf::InputStream を継承した自家製のクラスの使い方です。
まずは自家製のストリームクラスのインスタンスを生成。
データを読み込んだ後、そのデータを使いたいオブジェクトの loadFromStream()関数(または openFromStream()関数) に、その自家製ストリームを渡します。
// 自家製ストリームで 画像を読み込んで、
sf::FileStream stream;
stream.open("image.png");

// テクスチャとして使う
sf::Texture texture;
texture.loadFromStream(stream);
サンプル
コードのカラクリを勉強できるようなサンプルを見たい? 実装面の細かいところで迷子にならなくて済むようなわかりやすいサンプルが欲しい? ならば、sf::FileInputStream か sf::MemoryInputStream のソースを見るとよいですよ。

ネットを検索するのも忘れずに。運が良ければ誰かが既にキミのニーズに合った sf::InputStream(の、子クラス)を作って公開してるかもしれません。 または逆に、キミが作ったクラスがなんかいい感じっぽい気がしたら恥ずかしがらずに公開すべし!

※ 訳注 ※
SFML2.2 のチュートリアル原文にはサンプルが掲載されていたのですが、2.3以降のチュートリアルでは該当の箇所がごっそりと削除されています。 興味のある方は参考までにどうぞ(ページ内「実装例」の項)。
よくあるミス
リソースクラスの種類によっては、 一度 loadFromStream() を実行しただけでは読み込みが完了しないケースがあります。 そのリソースを使い続けている間、読み込みも続く。
たとえば、サウンドデータ(sf::Music)では、音声を再生している間、ストリーミングが続行します。
フォント(sf::Font)では、表示されるテキストに応じて文字の形状(グリフ)を取得し続けます。

つまり、そういうストリームクラスのインスタンス(と、元データ)は、 そのリソースが使われる間は存在し続けている必要があります。
まだ使われているのにインスタンスが破棄されてしまったら、何が起きるのかはパルプンテです (何も表示されない or 崩れたデータ or クラッシュ or とてつもなく……)。

もう1つのよくあるミスは、内部の実装に使った関数の戻り値をそのまま使ってしまうことです。
そのせいで、SFMLのストリームの仕様通りではなくなってしまう。
たとえば、sf::FileInputStream を継承したクラスを作るとき、 うっかりな誰かさんは次のような実装をしたくなってしまうかもしれません。
sf::Int64 FileStream::seek(sf::Int64 position)
{
    return std::fseek(m_file, position, SEEK_SET);
}
これの何がダメなのか?
std::fseek は成功時に ゼロを返します。一方、SFMLの仕様では、新たな読み込み位置を返す、ということになってます。