Unravel Engine C++ Reference
Loading...
Searching...
No Matches
asset_manifest.cpp
Go to the documentation of this file.
1#include "asset_manifest.h"
2#include "asset_extensions.h"
3#include <logging/logging.h>
4#include <fstream>
5#include <vector>
10
11namespace unravel
12{
13namespace asset_compiler
14{
15
16namespace
17{
18auto hash_file(const fs::path& file_path) -> std::string
19{
20 const bool is_text = !ex::is_binary(file_path.extension().string());
21 return fs::hash_file_fingerprint(file_path, is_text);
22}
23} // namespace
24
25void asset_manifest::compute_source_fingerprint(const fs::path& source_key)
26{
27 compute_source_fingerprint(source_key, {});
28}
29
30void asset_manifest::compute_source_fingerprint(const fs::path& source_key,
31 const std::vector<fs::path>& dependency_paths)
32{
33 auto source_file_path = fs::resolve_protocol(source_key);
34 try
35 {
37 if(dependency_paths.empty())
38 {
39 source_fingerprint = hash_file(source_file_path);
40 if(source_fingerprint.empty())
41 {
42 APPLOG_ERROR("Failed to fingerprint source file: {}", source_file_path.string());
43 }
44 return;
45 }
46
47 // Source is watched separately and omitted from dependency_paths;
48 // still include it so the combined fingerprint tracks root edits.
49 std::vector<std::string> fingerprints;
50 fingerprints.reserve(dependency_paths.size() + 1);
51 {
52 const auto source_fingerprint_part = hash_file(source_file_path);
53 if(!source_fingerprint_part.empty())
54 {
55 fingerprints.push_back(source_fingerprint_part);
56 }
57 }
58 for(const auto& dep_path : dependency_paths)
59 {
60 fs::error_code dep_ec;
61 if(!fs::exists(dep_path, dep_ec) || dep_ec)
62 {
63 continue;
64 }
65 const auto fingerprint = hash_file(dep_path);
66 if(fingerprint.empty())
67 {
68 continue;
69 }
70 fingerprints.push_back(fingerprint);
71 }
72
73 if(fingerprints.empty())
74 {
76 return;
77 }
78 if(fingerprints.size() == 1)
79 {
80 source_fingerprint = fingerprints.front();
81 return;
82 }
84 fingerprints.data() + 1,
85 fingerprints.size() - 1);
86 }
87 catch(const std::exception& e)
88 {
89 APPLOG_ERROR("Exception while fingerprinting {}: {}", source_file_path.string(), e.what());
91 }
92}
93
94auto get_manifest_path(const fs::path& compiled_asset_path) -> fs::path
95{
96 return compiled_asset_path.string() + ".manifest";
97}
98
99auto save_manifest(const fs::path& manifest_path, const asset_manifest& manifest) -> bool
100{
101 try
102 {
103 std::ofstream file(manifest_path);
104 if(!file.is_open())
105 {
106 APPLOG_ERROR("Failed to open manifest file for writing: {}", manifest_path.string());
107 return false;
108 }
109 auto archive = ser20::create_oarchive_associative(file);
110 bool success = true;
111 success &= try_save(archive, ser20::make_nvp("source_fingerprint", manifest.source_fingerprint));
112 success &= try_save(archive, ser20::make_nvp("format_version", manifest.format_version));
113 success &= try_save(archive, ser20::make_nvp("fingerprint_version", manifest.fingerprint_version));
114 return success;
115 }
116 catch(const std::exception& e)
117 {
118 APPLOG_ERROR("Exception while saving manifest {}: {}", manifest_path.string(), e.what());
119 return false;
120 }
121}
122
123auto load_manifest(const fs::path& manifest_path, asset_manifest& manifest) -> bool
124{
125 try
126 {
127 std::ifstream file(manifest_path);
128 if(!file.is_open())
129 {
130 APPLOG_ERROR("Failed to open manifest file for reading: {}", manifest_path.string());
131 return false;
132 }
133 auto archive = ser20::create_iarchive_associative(file);
134 try_load(archive, ser20::make_nvp("source_fingerprint", manifest.source_fingerprint));
135 try_load(archive, ser20::make_nvp("format_version", manifest.format_version));
136 try_load(archive, ser20::make_nvp("fingerprint_version", manifest.fingerprint_version));
137 return true;
138 }
139 catch(const std::exception& e)
140 {
141 APPLOG_ERROR("Exception while loading manifest {}: {}", manifest_path.string(), e.what());
142 return false;
143 }
144}
145
146namespace
147{
148auto resolve_manifest_input_file(const fs::path& key) -> fs::path
149{
150 fs::path source_key = fs::convert_to_protocol(key);
151 source_key = fs::replace(source_key, ex::get_meta_directory(), ex::get_data_directory());
152 if(source_key.extension() == ".meta")
153 {
154 source_key.replace_extension();
155 }
156 return source_key;
157}
158} // namespace
159
161{
162 return manifest.fingerprint_version == fs::current_source_fingerprint_version;
163}
164
165auto is_source_file_changed(const fs::path& source_path, const asset_manifest& manifest) -> bool
166{
168 {
169 APPLOG_WARNING("Fingerprint algorithm changed for {}, recompilation needed", source_path.string());
170 return true;
171 }
172 auto source_key = resolve_manifest_input_file(source_path);
173 asset_manifest current_manifest(source_key);
174 current_manifest.compute_source_fingerprint(source_key);
175 return current_manifest.source_fingerprint != manifest.source_fingerprint;
176}
177
178auto is_source_file_changed(const fs::path& source_path,
179 const asset_manifest& manifest,
180 const std::vector<fs::path>& dependency_paths) -> bool
181{
183 {
184 APPLOG_WARNING("Fingerprint algorithm changed for {}, recompilation needed", source_path.string());
185 return true;
186 }
187 auto source_key = resolve_manifest_input_file(source_path);
188 asset_manifest current_manifest(source_key);
189 current_manifest.compute_source_fingerprint(source_key, dependency_paths);
190 return current_manifest.source_fingerprint != manifest.source_fingerprint;
191}
192
193auto is_compiled_format_changed(const fs::path& source_path, const asset_manifest& manifest) -> bool
194{
195 auto source_key = resolve_manifest_input_file(source_path);
196 auto extension = source_key.extension().string();
197 auto current_version = ex::get_format_version(extension);
198 return manifest.format_version != current_version;
199}
200} // namespace asset_compiler
201} // namespace unravel
#define APPLOG_WARNING(...)
Definition logging.h:19
#define APPLOG_ERROR(...)
Definition logging.h:20
auto get_data_directory(const std::string &prefix={}) -> std::string
auto get_meta_directory(const std::string &prefix={}) -> std::string
auto is_binary(const std::string &ex) -> bool
auto get_format_version() -> uint64_t
auto hash_file_fingerprint(const path &file_path, bool normalize_text_line_endings) -> std::string
Hash a file's contents. Text assets normalize CRLF to LF before hashing.
auto combine_file_fingerprints(const std::string &first_fingerprint, const std::string *additional_fingerprints, size_t additional_count) -> std::string
Combine per-file fingerprints (raw 128-bit digests) into one hex fingerprint.
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
constexpr uint64_t current_source_fingerprint_version
xxHash3 128-bit fingerprints (hex). Bump when the hashing or dependency-ordering scheme changes.
path replace(const path &_path, const path &_sequence, const path &_new_sequence)
Replacing any occurences of the specified path sequence with another.
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
auto create_oarchive_associative(std::ostream &stream)
auto create_iarchive_associative(std::istream &stream)
auto get_manifest_path(const fs::path &compiled_asset_path) -> fs::path
Generate manifest file path from compiled asset path.
auto is_compiled_format_changed(const fs::path &source_path, const asset_manifest &manifest) -> bool
auto is_fingerprint_algorithm_current(const asset_manifest &manifest) -> bool
auto save_manifest(const fs::path &manifest_path, const asset_manifest &manifest) -> bool
Save manifest to file.
auto is_source_file_changed(const fs::path &source_path, const asset_manifest &manifest) -> bool
Check if source file has changed compared to manifest (source-only)
auto load_manifest(const fs::path &manifest_path, asset_manifest &manifest) -> bool
Load manifest from file.
auto try_save(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
auto try_load(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
Manifest data for compiled assets.
std::string source_fingerprint
Content fingerprint of source inputs (xxHash3 128-bit hex).
void compute_source_fingerprint(const fs::path &source_key)
Compute fingerprint from source file only.
uint64_t fingerprint_version
Fingerprint algorithm version. Legacy manifests use 0 (SHA1).