1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
|
#pragma once
#include "StarJson.hpp"
#include "StarOrderedMap.hpp"
#include "StarRect.hpp"
#include "StarBiMap.hpp"
#include "StarThread.hpp"
#include "StarAssetSource.hpp"
#include "StarAssetPath.hpp"
#include "StarRefPtr.hpp"
namespace Star {
STAR_CLASS(Font);
STAR_CLASS(Audio);
STAR_CLASS(Image);
STAR_STRUCT(FramesSpecification);
STAR_CLASS(Assets);
STAR_CLASS(LuaContext);
STAR_EXCEPTION(AssetException, StarException);
// The contents of an assets .frames file, which can be associated with one or
// more images, and specifies named sub-rects of those images.
struct FramesSpecification {
// Get the target sub-rect of a given frame name (which can be an alias).
// Returns nothing if the frame name is not found.
Maybe<RectU> getRect(String const& frame) const;
// Converts to Json.
Json toJson() const;
// The full path to the .frames file from which this was loaded.
String framesFile;
// Named sub-frames
StringMap<RectU> frames;
// Aliases for named sub-frames, always points to a valid frame name in the
// 'frames' map.
StringMap<String> aliases;
};
// The assets system can load image, font, json, and data assets from a set of
// sources. Each source is either a directory on the filesystem or a single
// packed asset file.
//
// Assets is thread safe and performs TTL caching.
class Assets {
public:
struct Settings {
// TTL for cached assets
float assetTimeToLive;
// Audio under this length will be automatically decompressed
float audioDecompressLimit;
// Number of background worker threads
unsigned workerPoolSize;
// If given, if an image is unable to load, will log the error and load
// this path instead
Maybe<String> missingImage;
// Same, but for audio
Maybe<String> missingAudio;
// When loading assets from a directory, will automatically ignore any
// files whose asset paths matching any of the given patterns.
StringList pathIgnore;
// Same, but only ignores the file for the purposes of calculating the
// digest.
StringList digestIgnore;
};
enum class QueuePriority {
None,
Working,
PostProcess,
Load
};
enum class AssetType {
Json,
Image,
Audio,
Font,
Bytes
};
struct AssetId {
AssetType type;
AssetPath path;
bool operator==(AssetId const& assetId) const;
};
struct AssetIdHash {
size_t operator()(AssetId const& id) const;
};
struct AssetData {
virtual ~AssetData() = default;
// Should return true if this asset is shared and still in use, so freeing
// it from cache will not really free the resource, so it should persist in
// the cache.
virtual bool shouldPersist() const = 0;
double time = 0.0;
bool needsPostProcessing = false;
bool forcePersist = false;
};
struct JsonData : AssetData {
bool shouldPersist() const override;
Json json;
};
// Image data for an image, sub-frame, or post-processed image.
struct ImageData : AssetData {
bool shouldPersist() const override;
ImageConstPtr image;
// *Optional* sub-frames data for this image, only will exist when the
// image is a top-level image and has an associated frames file.
FramesSpecificationConstPtr frames;
// If this image aliases another asset entry, this will be true and
// shouldPersist will never be true (to ensure that this alias and its
// target can be removed from the cache).
bool alias = false;
};
struct AudioData : AssetData {
bool shouldPersist() const override;
AudioConstPtr audio;
};
struct FontData : AssetData {
bool shouldPersist() const override;
FontConstPtr font;
};
struct BytesData : AssetData {
bool shouldPersist() const override;
ByteArrayConstPtr bytes;
};
struct AssetFileDescriptor {
// The mixed case original source name;
String sourceName;
// The source that has the primary asset copy
AssetSourcePtr source;
// List of source names and sources for patches to this file.
List<pair<String, AssetSourcePtr>> patchSources;
};
Assets(Settings settings, StringList assetSources);
~Assets();
void hotReload() const;
// Returns a list of all the asset source paths used by Assets in load order.
StringList assetSources() const;
// Return metadata for the given loaded asset source path
JsonObject assetSourceMetadata(String const& sourcePath) const;
// An imperfect sha256 digest of the contents of all combined asset sources.
// Useful for detecting if there are mismatched assets between a client and
// server or if assets sources have changed from a previous load.
ByteArray digest() const;
// Is there an asset associated with the given path? Path must not contain
// sub-paths or directives.
bool assetExists(String const& path) const;
Maybe<AssetFileDescriptor> assetDescriptor(String const& path) const;
// The name of the asset source within which the path exists.
String assetSource(String const& path) const;
Maybe<String> assetSourcePath(AssetSourcePtr const& source) const;
// Scans for all assets with the given suffix in any directory.
StringList scan(String const& suffix) const;
// Scans for all assets matching both prefix and suffix (prefix may be, for
// example, a directory)
StringList scan(String const& prefix, String const& suffix) const;
// Scans all assets for files with the given extension, which is specially
// indexed and much faster than a normal scan. Extension may contain leading
// '.' character or it may be omitted.
CaseInsensitiveStringSet const& scanExtension(String const& extension) const;
// Get json asset with an optional sub-path. The sub-path portion of the
// path refers to a key in the top-level object, and may use dot notation
// for deeper field access and [] notation for array access. Example:
// "/path/to/json:key1.key2.key3[4]".
Json json(String const& path) const;
// Either returns the json v, or, if v is a string type, returns the json
// pointed to by interpreting v as a string path.
Json fetchJson(Json const& v, String const& dir = "/") const;
// Load all the given jsons using background processing.
void queueJsons(StringList const& paths) const;
void queueJsons(CaseInsensitiveStringSet const& paths) const;
// Returns *either* an image asset or a sub-frame. Frame files are JSON
// descriptor files that reference a particular image and label separate
// sub-rects of the image. If the given path has a ':' sub-path, then the
// assets system will look for an associated .frames named either
// <full-path-minus-extension>.frames or default.frames, going up to assets
// root. May return the same ImageConstPtr for different paths if the paths
// are equivalent or they are aliases of other image paths.
ImageConstPtr image(AssetPath const& path) const;
// Load images using background processing
void queueImages(StringList const& paths) const;
void queueImages(CaseInsensitiveStringSet const& paths) const;
// Return the given image *if* it is already loaded, otherwise queue it for
// loading.
ImageConstPtr tryImage(AssetPath const& path) const;
// Returns the best associated FramesSpecification for a given image path, if
// it exists. The given path must not contain sub-paths or directives, and
// this function may return nullptr if no frames file is associated with the
// given image path.
FramesSpecificationConstPtr imageFrames(String const& path) const;
// Returns a pointer to a shared audio asset;
AudioConstPtr audio(String const& path) const;
// Load audios using background processing
void queueAudios(StringList const& paths) const;
void queueAudios(CaseInsensitiveStringSet const& paths) const;
// Return the given audio *if* it is already loaded, otherwise queue it for
// loading.
AudioConstPtr tryAudio(String const& path) const;
// Returns pointer to shared font asset
FontConstPtr font(String const& path) const;
// Returns a bytes asset (Reads asset as an opaque binary blob)
ByteArrayConstPtr bytes(String const& path) const;
// Bypass asset caching and open an asset file directly.
IODevicePtr openFile(String const& basePath) const;
// Clear all cached assets that are not queued, persistent, or broken.
void clearCache();
// Run a cleanup pass and remove any assets past their time to live.
void cleanup();
private:
EnumMap<AssetType> const AssetTypeNames{
{AssetType::Json, "json"},
{AssetType::Image, "image"},
{AssetType::Audio, "audio"},
{AssetType::Font, "font"},
{AssetType::Bytes, "bytes"}
};
static FramesSpecification parseFramesSpecification(Json const& frameConfig, String path);
void queueAssets(List<AssetId> const& assetIds) const;
//Lock before calling!
void queueAsset(AssetId const& assetId) const;
shared_ptr<AssetData> tryAsset(AssetId const& id) const;
shared_ptr<AssetData> getAsset(AssetId const& id) const;
void workerMain();
// All methods below assume that the asset mutex is locked when calling.
// Do some processing that might take a long time and should not hold the
// assets mutex during it. Unlocks the assets mutex while the function is in
// progress and re-locks it on return or before exception is thrown.
template <typename Function>
decltype(auto) unlockDuring(Function f) const;
// Returns the best frames specification for the given image path, if it exists.
FramesSpecificationConstPtr bestFramesSpecification(String const& basePath) const;
IODevicePtr open(String const& basePath) const;
ByteArray read(String const& basePath) const;
ImageConstPtr readImage(String const& path) const;
Json readJson(String const& basePath) const;
Json checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe<Json> const external) const;
// Load / post process an asset and log any exception. Returns true if the
// work was performed (whether successful or not), false if the work is
// blocking on something.
bool doLoad(AssetId const& id) const;
bool doPost(AssetId const& id) const;
// Assets can recursively depend on other assets, so the main entry point for
// loading assets is in this separate method, and is safe for other loading
// methods to call recursively. If there is an error loading the asset, this
// method will throw. If, and only if, the asset is blocking on another busy
// asset, this method will return null.
shared_ptr<AssetData> loadAsset(AssetId const& id) const;
shared_ptr<AssetData> loadJson(AssetPath const& path) const;
shared_ptr<AssetData> loadImage(AssetPath const& path) const;
shared_ptr<AssetData> loadAudio(AssetPath const& path) const;
shared_ptr<AssetData> loadFont(AssetPath const& path) const;
shared_ptr<AssetData> loadBytes(AssetPath const& path) const;
shared_ptr<AssetData> postProcessAudio(shared_ptr<AssetData> const& original) const;
// Updates time on the given asset (with smearing).
void freshen(shared_ptr<AssetData> const& asset) const;
Settings m_settings;
mutable Mutex m_assetsMutex;
mutable ConditionVariable m_assetsQueued;
mutable OrderedHashMap<AssetId, QueuePriority, AssetIdHash> m_queue;
mutable ConditionVariable m_assetsDone;
mutable HashMap<AssetId, shared_ptr<AssetData>, AssetIdHash> m_assetsCache;
mutable StringMap<String> m_bestFramesFiles;
mutable StringMap<FramesSpecificationConstPtr> m_framesSpecifications;
// Lua
RefPtr<RefCounter> m_luaEngine; // dumb but to avoid including Lua.hpp in here...
mutable HashMap<pair<AssetSource*, String>, LuaContextPtr> m_patchContexts;
mutable RecursiveMutex m_luaMutex;
// Paths of all used asset sources, in load order.
StringList m_assetSources;
// Maps an asset path to the loaded asset source and vice versa
BiMap<String, AssetSourcePtr> m_assetSourcePaths;
// Maps the source asset name to the source containing it
CaseInsensitiveStringMap<AssetFileDescriptor> m_files;
// Maps an extension to the files with that extension
CaseInsensitiveStringMap<CaseInsensitiveStringSet> m_filesByExtension;
ByteArray m_digest;
List<ThreadFunction<void>> m_workerThreads;
atomic<bool> m_stopThreads;
};
}
|