libeuropa/io: clean up PakWriter a bit more

also use visitor pattern instead of get_if chain
This commit is contained in:
Lily Tsuru 2025-01-07 14:10:03 -05:00
parent 7cbff3d81f
commit 175a5ff40a
3 changed files with 52 additions and 34 deletions

View file

@ -73,6 +73,11 @@ namespace europa::io {
return std::get_if<T>(&variant_); return std::get_if<T>(&variant_);
} }
template <class Visitor>
auto Visit(Visitor&& v) const {
return std::visit(v, variant_);
}
// private: // private:
PakFileData::Variant variant_; PakFileData::Variant variant_;
}; };

View file

@ -14,34 +14,35 @@
#include <iosfwd> #include <iosfwd>
#include <string> #include <string>
#include <utility> #include <utility>
#include "europa/structs/Pak.hpp" #include "europa/structs/Pak.hpp"
namespace europa::io { namespace europa::io {
/** /// A efficient writer for Europa package (.pak) files.
* Writer for package files.
*/
struct PakWriter { struct PakWriter {
/// Vocabulary type for making sector alignment stuff a bit easier to see.
enum class SectorAlignment {
DoNotAlign, /// Do not align to a sector boundary
Align /// Align to a sector boundary
};
using FlattenedType = std::pair<std::string, PakFile>; using FlattenedType = std::pair<std::string, PakFile>;
//void Init(structs::PakHeader::Version version); /// Initalize for the given package version.
//const HeaderType& GetHeader() const { return pakHeader; }
void SetVersion(structs::PakVersion version); void SetVersion(structs::PakVersion version);
/** /// Write archive to the given output stream.
* Write the resulting archive to the given output stream. /// [vec] is all files which should be packaged
*/ /// [sink] is a implementation of PakProgressReportsSink which should get events (TODO: Make this optional)
void Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink); /// [sectorAlignment] controls sector alignment
void Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, SectorAlignment sectorAlignment = SectorAlignment::DoNotAlign);
private: private:
template <class T>
void WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, SectorAlignment sectorAlignment);
template<class T> structs::PakVersion version {};
void WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, bool sectorAligned = false);
structs::PakVersion version{};
//HeaderType pakHeader {};
}; };
} // namespace europa::io } // namespace europa::io

View file

@ -15,6 +15,7 @@
#include <stdexcept> #include <stdexcept>
#include "europa/structs/Pak.hpp" #include "europa/structs/Pak.hpp"
#include "europa/util/Overloaded.hpp"
#include "StreamUtils.h" #include "StreamUtils.h"
namespace europa::io { namespace europa::io {
@ -34,16 +35,18 @@ namespace europa::io {
return (-value) & alignment - 1; return (-value) & alignment - 1;
} }
void PakWriter::Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink) { void PakWriter::Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, SectorAlignment sectorAlignment) {
// Depending on the version, do a mix of runtime/compile-time dispatch to the right
// package format version we have been told to write.
switch(version) { switch(version) {
case structs::PakVersion::Ver3: case structs::PakVersion::Ver3:
WriteImpl<structs::PakHeader_V3>(os, std::move(vec), sink); WriteImpl<structs::PakHeader_V3>(os, std::move(vec), sink, sectorAlignment);
break; break;
case structs::PakVersion::Ver4: case structs::PakVersion::Ver4:
WriteImpl<structs::PakHeader_V4>(os, std::move(vec), sink); WriteImpl<structs::PakHeader_V4>(os, std::move(vec), sink, sectorAlignment);
break; break;
case structs::PakVersion::Ver5: case structs::PakVersion::Ver5:
WriteImpl<structs::PakHeader_V5>(os, std::move(vec), sink); WriteImpl<structs::PakHeader_V5>(os, std::move(vec), sink, sectorAlignment);
break; break;
default: default:
throw std::invalid_argument("Invalid version"); throw std::invalid_argument("Invalid version");
@ -51,7 +54,7 @@ namespace europa::io {
} }
template <class THeader> template <class THeader>
void PakWriter::WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, bool sectorAligned) { void PakWriter::WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, SectorAlignment sectorAlignment) {
std::vector<FlattenedType> sortedFiles = std::move(vec); std::vector<FlattenedType> sortedFiles = std::move(vec);
THeader pakHeader {}; THeader pakHeader {};
@ -71,7 +74,7 @@ namespace europa::io {
} }
// Align first file to sector boundary. // Align first file to sector boundary.
if(sectorAligned) if(sectorAlignment == SectorAlignment::Align)
os.seekp( os.seekp(
AlignBy(os.tellp(), kCDSectorSize), AlignBy(os.tellp(), kCDSectorSize),
std::istream::beg); std::istream::beg);
@ -88,21 +91,30 @@ namespace europa::io {
auto& fileData = file.GetData(); auto& fileData = file.GetData();
// FIXME: use a visitor or something. For now I'm lazy and this should work // Visit the file data sum type and do the right operation
if(auto* path = fileData.template GetIf<std::filesystem::path>(); path) { // For filesystem paths, we open the file and then tee it into the package file
auto fs = std::ifstream((*path).string(), std::ifstream::binary); // effiently saving a lot of memory usage when packing (trading off some IO overhead,
if(!fs) // but hey.)
throw std::runtime_error("couldnt open input file? HOW");
// tee data from the file stream efficiently // clang-format off
impl::TeeInOut(fs, os); fileData.Visit(overloaded {
} else if(auto* buffer = fileData.template GetIf<std::vector<std::uint8_t>>(); buffer) { [&](const std::filesystem::path& path) {
// will eventually use this so we dont have to round trip to file IO probably auto fs = std::ifstream(path.string(), std::ifstream::binary);
os.write(reinterpret_cast<const char*>((*buffer).data()), file.GetSize()); if(!fs)
} throw std::runtime_error("couldnt open input file? HOW");
// Tee data into the package file stream
impl::TeeInOut(fs, os);
},
[&](const std::vector<std::uint8_t>& buffer) {
os.write(reinterpret_cast<const char*>(buffer.data()), file.GetSize());
}
});
// clang-format on
// Align to sector boundary. // Align to sector boundary.
if(sectorAligned) if(sectorAlignment == SectorAlignment::Align)
os.seekp( os.seekp(
AlignBy(os.tellp(), kCDSectorSize), AlignBy(os.tellp(), kCDSectorSize),
std::istream::beg); std::istream::beg);