# APIs for Implicit Sharing Implicit sharing is a technique that avoid copying data when it is not necessary and thus improves memory usage and performance. Only read-only data can be shared because otherwise multiple owners might want to change the data in conflicting ways. It's called *implicit* sharing, because the sharing should be invisible to the user generally. To achieve this, some special care is necessary when designing APIs that involve implicitly shared data to ensure correctness and ease of use. This document is meant to provide guidelines for how an API should be designed so that it works well with implicit sharing. ## External `ImplicitSharingInfo` This is used when some "raw data" is shared, that does not have to know about implicit sharing at all. Typically, this is used when sharing simple arrays. Sometimes, those are even allocated outside of our control, so they can't e.g. be prefixed with a user count. Thus the `ImplicitSharingInfo` has to be stored separately. The API involves three different entities: * `Owner`: The object that references the possibly shared data. * `Data`: The actual data that we want to be shared. * `ImplicitSharingInfo`: Contains information about the current sharing state. ```mermaid flowchart TD Owner-->ImplicitSharingInfo; Owner-->Data; subgraph share [SharedData] Data ImplicitSharingInfo end ``` This structure is currently mainly used for attributes. For example, for the `position` attribute of a `Mesh`, the `Mesh` is the owner and the `float3 array` is the data. The API requires roughly functionality below. Note that these guidelines don't offer precise function signatures, because that depends on the exact use case. ```cpp /** * Simply receives a reference to the potentially shared data. * The caller should not edit the returned data, because it might be shared with others. * This method should be const, because it can be. * * Examples: * - const void *CustomData_get_layer(const CustomData *, ...) * - GAttributeReader AttributeAccessor::lookup(...) const * - Span<int> CurvesGeometry::offsets() const * - Span<float3> Mesh::vert_positions() const; */ const Data *Owner::get_data() const; /** * Get a mutable version of the data. If the owner currently points to shared data, * the data has to be duplicated and the owner has to change its internal pointer * to point to the new copied data. * This method should not be const, because it returns non-const internal data of * the owner. * * Examples: * - void *CustomData_get_for_write(CustomData *data, ...) * - GAttributeWriter MutableAttributeAccessor::lookup_for_write(...) * - MutableSpan<int> CurvesGeometry::offsets_for_write() * - MutableSpan<float3> Mesh::vert_positions_for_write() */ Data *Owner::get_data_for_write(); /** * This can be used to take shared ownership of the internal data. If the * `ImplicitSharingInfo` is returned as raw pointer, it is a borrowed reference. * That means, that no additional user has been added to it yet and the caller * would have to add that user manually in case he wants to take shared ownership. * * Examples: * - GAttributeReader AttributeAccessor::lookup(...) const */ [const Data *, const ImplicitSharingInfo *] Owner::get_data_for_share() const; ``` ## Integrated `ImplicitSharingInfo` The same three entities exist in this setup as well. However, now the data and the `ImplicitSharingInfo` are combined into one object. Typically, the data derives from `ImplicitSharingMixin` to achieve this. The owner only needs a single pointer to the shared data now. ```mermaid flowchart TD Owner-->share; subgraph share [SharedData] Data ImplicitSharingInfo end ``` This approach is currently mainly used for `GeometrySet` (owner) and `GeometryComponent` (shared data). ```cpp /** * Retrieve a reference to the data pointed to by the owner. * The caller should not edit the returned data because it might be shared with others. * The method should be const because it can be. * * Examples: * - const GeometryComponent *GeometrySet::get_component(...) const */ const SharedData *Owner::get() const; /** * Get a mutable version of the data. If the owner shares the data with some other * object, it has to be copied and the internal pointer in the owner has to be * updated to point to the copied value. This method should be non-const because * it returns a reference to non-const internal data. * * Examples: * - GeometryComponent &GeomtrySet::get_component_for_write(...) */ SharedData *Owner::get_for_write(); /** * Const methods on the shared data are expected to behave like const methods in general. * That means they have to be thread safe and the object remains logically const. * * Example: * - const Mesh *MeshComponent::get() const * - bool InstancesComponent::is_empty() const */ SomeValue SharedData::get_some_value() const; /** * Non-const methods on the shared data can assume and assert that it is mutable, i.e. * is not shared. The caller has to ensure that this is the case, which it typically does * by retrieving it from a `*_for_write` method. * * Example: * - Mesh *MeshComponent::release() * - void InstancesComponent::ensure_owns_direct_data() */ SomeValue SharedData::some_modification(); ```