介绍
SFML有几个资源类:图像、字体、声音等。在大多数程序中,这些资源将从文件中加载,借助于它们的loadFromFile函数。在另外一些情况下,资源将直接打包到可执行文件或大型数据文件中,并使用loadFromMemory从内存中加载。这些函数几乎涵盖了所有可能的用例,但不是全部。
有时,您想从不寻常的地方加载文件,例如压缩/加密存档或远程网络位置。对于这些特殊情况,SFML提供了第三个加载函数:loadFromStream。该函数使用抽象的sf::InputStream接口读取数据,该接口允许您提供自己实现的流类,该类可以与SFML一起使用。
在本教程中,您将学习如何编写和使用自己派生的输入流。
关于标准流
与许多其他语言一样,C++已经有了一个用于输入数据流的类:std::istream。实际上,它有两个:std::istream只是前端,用于自定义数据的抽象接口是std::streambuf。
不幸的是,这些类并不是非常用户友好的,如果您想要实现非平凡的东西,它们可能变得非常复杂。Boost.Iostreams库尝试为标准流提供更简单的接口,但Boost是一个庞大的依赖项,SFML不能依赖于它。
这就是为什么SFML提供了自己的流接口,希望它更简单和更快。
InputStream
sf::InputStream类声明了四个虚函数:
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字节的数据,并将它们复制到提供的数据地址。它返回读取的字节数,或者在出现错误时返回-1。
seek函数必须更改流中的当前读取位置。它 的position参数是要跳转到的绝对字节偏移量(因此它相对于数据的开头而不是当前位置)。它返回新位置,或在出现错误时返回-1。
tell函数必须返回流中的当前读取位置(以字节为单位),或者在出现错误时返回-1。
getSize函数必须返回包含在流中的数据的总大小(以字节为单位),或者在出现错误时返回-1。
要创建自己的工作流,必须根据它们的要求实现这四个函数中的每一个。
FileInputStream 和MemoryInputStream
从SFML 2.3开始,创建了两个新类来为新的内部音频管理提供流。sf::FileInputStream提供了文件的只读数据流,而sf::MemoryInputStream从内存中提供了只读流。它们都派生自sf::InputStream,因此可以使用多态。
使用InputStream
使用自定义流类很简单:实例化它,然后将其传递给要加载的对象的loadFromStream(或openFromStream)函数。
sf::FileStream stream;
stream.open("image.png");
sf::Texture texture;
texture.loadFromStream(stream);
例子
如果您需要一个演示,帮助您专注于代码的工作原理,而不是迷失于实现细节中,您可以查看sf::FileInputStream或sf::MemoryInputStream的实现。
不要忘记检查论坛和维基百科。 另一个用户可能已经编写了适合您需求的sf::InputStream类。如果您编写了一个新的类并且认为它也可能对其他人有用,请不要犹豫,分享它!
常见错误
在调用loadFromStream后,有些资源类并不完全加载。相反,只要它们在使用,它们就会继续从其数据源中读取数据。这适用于sf::Music,它会在播放时流式传输音频样本,以及sf::Font,它会根据显示的文本即时加载字形。
因此,您用于加载音乐或字体的流实例及其数据源必须在资源使用它的整个生命周期中保持活动状态。如果在仍在使用时销毁它,它将导致未定义的行为(可能会导致崩溃,数据损坏或无任何可见变化)。
另一个常见的错误是直接返回内部函数返回的任何内容,但有时它与SFML所期望的不匹配。例如,在sf::FileInputStream代码中,可能会尝试将seek函数编写如下:
sf::Int64 FileInputStream::seek(sf::Int64 position)
{
return std::fseek(m_file, position, SEEK_SET);
}
这段代码是错误的,因为std::fseek成功时返回零,而SFML期望返回新位置。