[Перевод] 2.3 Работа с пользовательскими потоками данных
От переводчика: данная статья является восьмой в цикле переводов официального руководства по библиотеке SFML. Прошлую статью можно найти тут. Данный цикл статей ставит своей целью предоставить людям, не знающим язык оригинала, возможность ознакомится с этой библиотекой. SFML — это простая и кроссплатформенная мультимедиа библиотека. SFML обеспечивает простой интерфейс для разработки игр и прочих мультимедийных приложений. Оригинальную статью можно найти тут. Начнем.
1. Приступая к работе
- SFML и Visual Studio
- SFML и Code: Blocks (MinGW)
- SFML и Linux
- SFML и Xcode (Mac OS X)
- Компиляция SFML с помощью CMake
2. Модуль System
- Обработка времени
- Потоки
- Работа с пользовательскими потоками данных
3. Модуль Window
- Открытие и управление окнами
- Обработка событий
- Работа с клавиатурой, мышью и джойстиками
- Использование OpenGL
4. Модуль Graphics
- Рисование 2D объектов
- Спрайты и текстуры
- Текст и шрифты
- Формы
- Проектирование ваших собственных объектов с помощью массивов вершин
- Позиция, вращение, масштаб: преобразование объектов
- Добавление специальных эффектов с шейдерами
- Контроль 2D камеры и вида
5. Модуль Audio
- Проигрывание звуков и музыки
- Запись аудио
- Пользовательские потоки аудио
- Спатиализация: звуки в 3D
6. Модуль Network
- Коммуникация с использованием сокетов
- Использование и расширение пакетов
- Веб-запросы с помощью HTTP
- Передача файлов с помощью FTP
Вступление
В SFML есть классы ресурсов (изображений, шрифтов, звуков и т.д.). Во многих программах эти ресурсы будут загружаться из файлов с помощью функции loadFromFile
. В некоторых ситуациях ресурсы будут непосредственно упакованы в исполняемый файл или в большой файл данных и будут загружаться из памяти с помощью функции loadFromMemory
. Эти функции могут удовлетворить практически любые потребности, но не все.
Вы можете захотеть загрузить файлы из необычного места, такого как сжатый/зашифрованный архив, или, например, из удаленной сетевой папки. Для этих особых ситуаций, SFML предоставляет третью функцию: loadFromStream
. Эта функция считывает данные, используя абстрактный интерфейс sf: InputStream, который позволяет вам иметь собственную реализацию класса потока, который будет работать с SFML.
В этой статье будет рассказано, как писать и использовать ваши собственные классы потоков.
Стандартные потоки C++?
Как и многие другие языки, C++ уже содержит класс потока данных: std::istream
. По факту, таких классов два: std::istream
является только front-end решением, абстрактным интерфейсом к данным, получаемым из std::streambuf
.
К сожалению, эти классы не дружелюбны к пользователю — решение нетривиальной задачи с использованием данных классов может стать очень сложным. Библиотека Boost.Iostreams стремится обеспечить простой интерфейс для стандартных потоков, но Boost — это большая зависимость.
По этой причине 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 должна изменять текущую позицию в потоке. Аргумент является абсолютным числом байтов для перемещения (отсчет идет от начала области данных, а не от текущей позиции). Возвращает новую позицию или -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
.
Не забудьте проверить форум и вики. Есть шанс, что другой пользователь уже написал класс, который соответствует вашим потребностям. Если вы написали новый класс и считаете, что он может быть полезен другим пользователям, не стесняйтесь поделиться им!
Распространенные заблуждения
Некоторые классы ресурсов не загружаются полностью после вызова loadFromStream
. Вместо этого, они продолжают читать данные из источника данных так долго, как это требуется. По этой причине класс sf: Music читает аудио примитивы через поток на протяжении их проигрывания, а класс sf: Font загружает глифы, когда в этом возникает необходимость.
Как следствие, поток, используемый вами для загрузки музыки или шрифта, также, как и источник данных, должны существовать так долго, как долго эти ресурсы используются. Разрушение источника информации или класса потока данных приводит к неопределенному поведению (программа может завершить свое выполнение, повредить данные, а может ничего не произойти).
Другим распространенным заблуждением является возврат функцией неверного значения, т.е. значения, возврат которого не ожидается SFML в данной ситуации. Например, для sf::FileInputStream
можно было бы написать функцию поиска следующим образом:
sf::Int64 FileInputStream::seek(sf::Int64 position)
{
return std::fseek(m_file, position, SEEK_SET);
}
Однако этот код не верен, потому что std::fseek
возвращает нуль при успехе, в то время как SFML ожидает возврата новой позиции в потоке.