Initialize member variables when declaring them for private classes
[alure.git] / examples / alure-physfs.cpp
blobd2d7f3b5a7396c7dad1fd518f6abd9c54621ea98
1 /*
2 * An example showing how to read files using custom I/O routines. This
3 * specific example uses PhysFS to read files from zip, 7z, and some other
4 * archive formats.
5 */
7 #include <iostream>
8 #include <iomanip>
9 #include <cstring>
10 #include <limits>
11 #include <thread>
12 #include <chrono>
14 #include "physfs.h"
16 #include "alure2.h"
18 namespace
21 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
22 class StreamBuf final : public std::streambuf {
23 using BufferArrayT = alure::Array<char_type,4096>;
24 BufferArrayT mBuffer;
25 PHYSFS_File *mFile{nullptr};
27 int_type underflow() override
29 if(mFile && gptr() == egptr())
31 // Read in the next chunk of data, and set the read pointers on
32 // success
33 PHYSFS_sint64 got = PHYSFS_read(mFile,
34 mBuffer.data(), sizeof(char_type), mBuffer.size()
36 if(got != -1) setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
38 if(gptr() == egptr())
39 return traits_type::eof();
40 return traits_type::to_int_type(*gptr());
43 pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
45 if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
46 return traits_type::eof();
48 // PhysFS only seeks using absolute offsets, so we have to convert cur-
49 // and end-relative offsets.
50 PHYSFS_sint64 fpos;
51 switch(whence)
53 case std::ios_base::beg:
54 break;
56 case std::ios_base::cur:
57 // Need to offset for the read pointers with std::ios_base::cur
58 // regardless
59 offset -= off_type(egptr()-gptr());
60 if((fpos=PHYSFS_tell(mFile)) == -1)
61 return traits_type::eof();
62 offset += fpos;
63 break;
65 case std::ios_base::end:
66 if((fpos=PHYSFS_fileLength(mFile)) == -1)
67 return traits_type::eof();
68 offset += fpos;
69 break;
71 default:
72 return traits_type::eof();
74 // Range check - absolute offsets cannot be less than 0, nor be greater
75 // than PhysFS's offset type.
76 if(offset < 0 || offset >= std::numeric_limits<PHYSFS_sint64>::max())
77 return traits_type::eof();
78 if(PHYSFS_seek(mFile, PHYSFS_sint64(offset)) == 0)
79 return traits_type::eof();
80 // Clear read pointers so underflow() gets called on the next read
81 // attempt.
82 setg(0, 0, 0);
83 return offset;
86 pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
88 // Simplified version of seekoff
89 if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
90 return traits_type::eof();
92 if(pos < 0 || pos >= std::numeric_limits<PHYSFS_sint64>::max())
93 return traits_type::eof();
94 if(PHYSFS_seek(mFile, PHYSFS_sint64(pos)) == 0)
95 return traits_type::eof();
96 setg(0, 0, 0);
97 return pos;
100 public:
101 bool open(const char *filename) noexcept
103 mFile = PHYSFS_openRead(filename);
104 if(!mFile) return false;
105 return true;
108 StreamBuf() = default;
109 ~StreamBuf() override
111 PHYSFS_close(mFile);
112 mFile = nullptr;
116 // Inherit from std::istream to use our custom streambuf
117 class Stream final : public std::istream {
118 public:
119 Stream(const char *filename) : std::istream(new StreamBuf())
121 // Set the failbit if the file failed to open.
122 if(!(static_cast<StreamBuf*>(rdbuf())->open(filename)))
123 clear(failbit);
125 ~Stream() override
126 { delete rdbuf(); }
129 // Inherit from alure::FileIOFactory to use our custom istream
130 class FileFactory final : public alure::FileIOFactory {
131 public:
132 FileFactory(const char *argv0)
134 // Need to initialize PhysFS before using it
135 if(PHYSFS_init(argv0) == 0)
136 throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") +
137 PHYSFS_getLastError());
139 std::cout<< "Initialized PhysFS, supported archive formats:" <<std::endl;
140 for(const PHYSFS_ArchiveInfo **i = PHYSFS_supportedArchiveTypes();*i != NULL;i++)
141 std::cout<< " "<<(*i)->extension<<": "<<(*i)->description <<std::endl;
142 std::cout<<std::endl;
144 ~FileFactory() override
146 PHYSFS_deinit();
149 alure::UniquePtr<std::istream> openFile(const alure::String &name) noexcept override
151 auto stream = alure::MakeUnique<Stream>(name.c_str());
152 if(stream->fail()) stream = nullptr;
153 return std::move(stream);
156 // A PhysFS-specific function to mount a new path to the virtual directory
157 // tree.
158 static bool Mount(const char *path, const char *mountPoint=nullptr, int append=0)
160 std::cout<< "Adding new file source "<<path;
161 if(mountPoint) std::cout<< " to "<<mountPoint;
162 std::cout<<"..."<<std::endl;
164 if(PHYSFS_mount(path, mountPoint, append) == 0)
166 std::cerr<< "Failed to add "<<path<<": "<<PHYSFS_getLastError() <<std::endl;
167 return false;
169 return true;
172 static void ListDirectory(std::string&& dir)
174 char **files = PHYSFS_enumerateFiles(dir.c_str());
175 for(int i = 0;files[i];i++)
177 std::string file = dir + files[i];
178 if(PHYSFS_isDirectory(file.c_str()))
179 ListDirectory(file+"/");
180 else
181 std::cout<<" "<<file<<"\n";
183 PHYSFS_freeList(files);
187 } // namespace
190 int main(int argc, char *argv[])
192 if(argc < 2)
194 std::cerr<< "Usage: "<<argv[0]<<" [-device <device name>]"
195 " -add <directory | archive> file_entries ..." <<std::endl;
196 return 1;
199 // Set our custom factory for file IO. From now on, all filenames given to
200 // Alure will be used with our custom factory.
201 alure::FileIOFactory::set(alure::MakeUnique<FileFactory>(argv[0]));
203 alure::DeviceManager devMgr = alure::DeviceManager::get();
205 int fileidx = 1;
206 alure::Device dev;
207 if(argc > 3 && strcmp(argv[1], "-device") == 0)
209 fileidx = 3;
210 dev = devMgr.openPlayback(argv[2], std::nothrow);
211 if(!dev)
212 std::cerr<< "Failed to open \""<<argv[2]<<"\" - trying default" <<std::endl;
214 if(!dev)
215 dev = devMgr.openPlayback();
216 std::cout<< "Opened \""<<dev.getName()<<"\"" <<std::endl;
218 alure::Context ctx = dev.createContext();
219 alure::Context::MakeCurrent(ctx);
221 for(int i = fileidx;i < argc;i++)
223 if(alure::StringView("-add") == argv[i] && argc-i > 1)
225 FileFactory::Mount(argv[++i]);
226 std::cout<<"Available files:\n";
227 FileFactory::ListDirectory("/");
228 std::cout.flush();
229 continue;
232 alure::SharedPtr<alure::Decoder> decoder(ctx.createDecoder(argv[i]));
233 alure::Source source = ctx.createSource();
234 source.play(decoder, 12000, 4);
235 std::cout<< "Playing "<<argv[i]<<" ("<<alure::GetSampleTypeName(decoder->getSampleType())<<", "
236 <<alure::GetChannelConfigName(decoder->getChannelConfig())<<", "
237 <<decoder->getFrequency()<<"hz)" <<std::endl;
239 float invfreq = 1.0f / decoder->getFrequency();
240 while(source.isPlaying())
242 std::cout<< "\r "<<std::fixed<<std::setprecision(2)<<
243 source.getSecOffset().count()<<" / "<<(decoder->getLength()*invfreq);
244 std::cout.flush();
245 std::this_thread::sleep_for(std::chrono::milliseconds(25));
246 ctx.update();
248 std::cout<<std::endl;
250 source.release();
251 decoder.reset();
254 alure::Context::MakeCurrent(nullptr);
255 ctx.destroy();
256 dev.close();
258 return 0;