Page MenuHome

Asset System: Technical Design
Confirmed, NormalPublicDESIGN

Description

Asset System: Technical Design

Collaborative design work by @Sybren A. Stüvel (sybren) and @Julian Eisel (Severin).

For the general Asset Browser design and Add-on access to it, we'll need some kind of back-end to handle asset reading, asset storage, asset library management, asset display, etc.

The current system in master (experimental feature) is mostly limited to the Asset Browser, so all that can be done by the File Browser editor (which the Asset Browser is using), much of it was already there for managing files.
However there is currently an asset view UI template in the works, that supports displaying assets anywhere in the UI, so it has to work outside the File/Asset Browser context. Plus, in future we want to allow Add-ons to register custom libraries, dynamically provide lists of assets, provide non-Blender data-block assets, etc.

Given the importance of asset management, we want to have a well thought out design in place, with well defined interfaces for general Blender code and Python access.


Initial design of the main interfaces and how that can be translated to the Python API.

We mocked up the main design units as code here, which for us (being coders) is a simple way to get a good grip of the design.

C++ Mockup

The highest level entity is the Asset System and provides basic functionality with global effect.

using AssetListReceiver =
    void (*)(AssetRepresentation asset,
             // For progress report, like "this is number 5 of the 10 assets 
             // available". Note that BOTH can increase over time, for example
             // when more results are found, `total_asset_count` can increase.
             size_t total_asset_count,
             size_t asset_index);

/** AssetSystem manages multiple asset libraries. */
class AssetSystem {
 public:
  void register_library(AssetLibrary library);
  void set_active_library(AssetLibrary library);
  AssetLibrary get_active_library();

  // Query the available assets of the active library that match the given
  // filter.
  void available_assets(AssetFilter filter, AssetListReceiver receiver);
};

The AssetListReceiver and available_assets() part may be superseded by the asset storage design (see T88184: Asset System: Data Storage, Reading & UI Access). In there the storage is managed through AssetList and AssetListStorage (better separation of concerns). AssetSystem can be a mere interface.


/**
 * AssetIdentifier contains enough information to find the AssetLibrary that
 * contains it, and for that library to load the asset.
 */
class AssetIdentifier {
 public:
  // URIs are one example of suitable identifiers, for example:
  //   cloud://cloud.blender.org/project/asset/file.jpg
  //   file:///path/to/project/asset/file.blend/Actions/MyPose
  //   smb://nas.mystudio/assets/file.blend/Objects/SuperSintel
  static AssetIdentifier from_uri(std::string_view uri);
  std::string as_uri() const;
};

It's unclear still if we need such full URIs in practice. It may also make sense to let asset management add-ons opt-out of URIs and use custom identifiers. The identifier could be just an abstract class with a purely virtual comparison operator.

using AssetReceiver = void (*)(AssetRepresentation asset);
using AssetErrorCallback = void (*)();  // TBD signature

/**
 * AssetLibrary manages a single library. This is just for information about
 * the library. The actual storage of assets is handled by #AssetList, and the
 * global #AssetListStorage.
 */
class AssetLibrary {
  // Expose the 'protocol://host' part supported by this library, so that
  // AssetIdentifiers can be matched to a library that supports it.
  std::string uri_protocol() const;
  std::string uri_host() const;  // can be empty to indicate "any host"

  // Unsure whether this should be per AssetLibrary or on the AssetSystem (or
  // both).
  void register_error_callback(AssetErrorCallback callback);

  // Fast, as it only needs metadata and not the asset itself.
  void load_asset_repr(AssetIdentifier identifier, AssetReceiver callback);

  // Loads the actual asset, so could take more time.
  enum class LoadType {
    LINK,
    APPEND,
    NOMAIN,
  };
  void load_asset(AssetIdentifier identifier,
                  LoadType load_type,
                  AssetReceiver callback);
};
/**
 * AssetRepresentation represents the asset, and contains enough information to
 * show it in the UI (name, description, tags, preview, etc.).
 *
 * Information that takes longer to load, like previews, could be loaded
 * asynchronously and become available later.
 *
 * There could be several singletons with their own semantics, for example one
 * for 'data is still loading' or 'this could get expanded into several assets'.
 *
 * When the asset is loaded & available in memory, this also provides access to
 * it via the `as_id()` function. Not sure about this design choice, though.
 */
class AssetRepresentation {
  AssetIdentifier identifier;

  ID *as_id_datablock();  // Valid once actually loaded.
  
  /**
   * The preview-image for this asset, if available (local or )
   */
  PreviewImage *preview();
  /**
   * Path relative to the containing asset library. The asset-library and 
   * asset-list provide helpers to get the full path from this
   * #AssetRepresentation.
   */
  string relative_path();
};

We may want to differentiate between local and external assets here. AssetRepresentation could also be an (abstract) base class with LocalAssetRepresentation and ExternalAssetRepresentation as implementations.

This class needs a handle for C and RNA. E.g. so that operators can get asset information from context.

/** A handle as C alias. */
typedef struct AssetRepresentation * AssetHandle;

Python API

Most (but not all!) asset information is independent of the current file loaded. Exceptions are the Current File and the planned Current Project library. The Python API needs to work independent of bpy.data, but can do some interactions on it (e.g. after getting the ID of a local asset).

Therefore, a new module might make sense.

import asset_system

def register():
  asset_system.register_library(...)

# Example usage
def some_func():
  from asset_system.types import AssetLibrary
  
  library: AssetLibrary = asset_system.find_library(some_identifier)
  library.do_stuff()

Although maybe it shoudn't be a standalone module, it could be bpy.asset_system instead.

The API would then simply wrap the internal C/C++ API.