#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 getRect(String const& frame) const; // The full path to the .frames file from which this was loaded. String framesFile; // Named sub-frames StringMap frames; // Aliases for named sub-frames, always points to a valid frame name in the // 'frames' map. StringMap 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 missingImage; // Same, but for audio Maybe 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 AssetType { Json, Image, Audio, Font, Bytes }; enum class QueuePriority { None, Working, PostProcess, Load }; 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; }; 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> patchSources; }; Assets(Settings settings, StringList assetSources); ~Assets(); // 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 assetDescriptor(String const& path) const; // The name of the asset source within which the path exists. String assetSource(String const& path) const; Maybe 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. StringSet 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(StringSet 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 // .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(StringSet 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(StringSet 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: static FramesSpecification parseFramesSpecification(Json const& frameConfig, String path); void queueAssets(List const& assetIds) const; //Lock before calling! void queueAsset(AssetId const& assetId) const; shared_ptr tryAsset(AssetId const& id) const; shared_ptr 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 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 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 loadAsset(AssetId const& id) const; shared_ptr loadJson(AssetPath const& path) const; shared_ptr loadImage(AssetPath const& path) const; shared_ptr loadAudio(AssetPath const& path) const; shared_ptr loadFont(AssetPath const& path) const; shared_ptr loadBytes(AssetPath const& path) const; shared_ptr postProcessAudio(shared_ptr const& original) const; // Updates time on the given asset (with smearing). void freshen(shared_ptr const& asset) const; Settings m_settings; mutable Mutex m_assetsMutex; mutable ConditionVariable m_assetsQueued; mutable OrderedHashMap m_queue; mutable ConditionVariable m_assetsDone; mutable HashMap, AssetIdHash> m_assetsCache; mutable StringMap m_bestFramesFiles; mutable StringMap m_framesSpecifications; // Lua RefPtr m_luaEngine; // dumb but to avoid including Lua.hpp in here... mutable StringMap 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 m_assetSourcePaths; // Maps the source asset name to the source containing it CaseInsensitiveStringMap m_files; // Maps an extension to the files with that extension CaseInsensitiveStringMap m_filesByExtension; ByteArray m_digest; List> m_workerThreads; atomic m_stopThreads; }; }