OpenAssetIO icon indicating copy to clipboard operation
OpenAssetIO copied to clipboard

C API for `EntityReference`

Open feltech opened this issue 3 years ago • 2 comments

What

Following on from #549, we must implement the C API for the EntityReference class and its related Manager member functions.

Why

This is a requirement for a functional C host.

Notes

Design sketch:


/**
 * Entity reference destructor.
 *
 * Note: not paired with a constructor, since
 * `Manager.createEntityReference` should always be used.
 */
void oa_EntityReference_dtor(oa_EntityReference_h handle) {
  EntityReference* entityReference = handles::EntityReference::toInstance(handle);
  delete entityReference;
}

/**
 * Get a copy of the underlying entity reference string.
 *
 * Assumes noexcept.
 */
void oa_EntityReference_toString(oa_StringView* out, oa_EntityReference_h handle) {
  const EntityReference* entityReference = handles::EntityReference::toInstance(handle);
  openassetio::assignStringView(out, entityReference->toString());
}

/**
 * Check whether a given string is an entity reference relevant
 * to the manager.
 *
 * Assumes noexcept.
 */
bool oa_hostApi_Manager_isEntityReferenceString(oa_hostApi_Manager_h handle,
                                                oa_ConstStringView entityReferenceString) {
  const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);
  return manager->isEntityReferenceString(
      {entityReferenceString.data, entityReferenceString.size});
}

/**
 * Construct an EntityReference.
 *
 * This is the only way of creating an EntityReference in the C API. The caller is responsible
 * for cleaning it up with oa_EntityReference_dtor.
 */
oa_ErrorCode oa_hostApi_Manager_createEntityReference(oa_StringView* err,
                                                      oa_EntityReference_h* out,
                                                      oa_hostApi_Manager_h handle,
                                                      oa_ConstStringView entityReferenceString) {
  return errors::catchUnknownExceptionAsCode(err, [&] {
    const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);

    std::optional<EntityReference> maybeEntityRef = manager->createEntityReferenceIfValid(
        {entityReferenceString.data, entityReferenceString.size});

    if (!maybeEntityRef) {
      openassetio::assignStringView(err, "Invalid entity reference");
      return oa_ErrorCode_kInvalidEntityReference;
    }

    *out = handles::EntityReference::toHandle(new EntityReference{std::move(*maybeEntityRef)});
    return oa_ErrorCode_kOK;
  });
}

feltech avatar Aug 09 '22 09:08 feltech

Whilst sketching out the C API for BatchElementError in #557, it became apparent that we may need additional C functions for constructing a batch of entity reference objects.

For batch functions such as resolve we need to take a list of EntitiyReference objects. Naively, these could be created one-by-one using oa_hostApi_Manager_createEntityReference, and added to a C array of oa_EntityReference_h handles.

In C++ terms we then have a C array of pointers to EntityReference objects. The current design of our batch methods takes a std::vector<EntityReference> - i.e. a contiguous array. To convert from an array of pointers to a contiguous array means copying every element.

So we may wish to bind an oa_EntityReferenceList_h type, which wraps a std::vector, and add oa_hostApi_Manager_addEntityReference-like functions that append to the underlying std::vector. This would then be trivial to unpack in the C->C++ API.

This was actually assumed in a previous sketch: https://github.com/OpenAssetIO/OpenAssetIO/issues/490#issuecomment-1170008687, where the entity references argument to resolve is an unspecified oa_ConstEntityRefs_h. That sketch pre-dates the strongly typed EntityReference object, but the principle is the same.

feltech avatar Aug 12 '22 09:08 feltech

A potential improvement to the proposed sketch would be to alter the argument oa_EntityReference_h* out to oa_EntityReference_h out, i.e. expect a pre-allocated EntityReference, which will be "filled in". This allows the host to re-use storage, rather than necessitate a dynamic allocation with every createEntityReference call.

Such a scheme would be particularly useful with respect to the above comment, i.e. for building a vector of entity references.

E.g.

  oa_EntityReferenceList_h refListHandle = oa_EntityReferenceList_ctor();
  oa_EntityReference_h refHandle = oa_EntityReference_ctor();

  oa_hostApi_Manager_createEntityReference(err, refHandle, managerHandle, firstRefString);
  // Assumed a `move` function that uses `std::move` internally, leaving
  // the `refHandle` empty, but reusable.
  oa_EntityReferenceList_move(refListHandle, refHandle)

  oa_hostApi_Manager_createEntityReference(err, refHandle, managerHandle, secondRefString);
  oa_EntityReferenceList_move(refListHandle, refHandle)

  oa_EntityReference_dtor(refHandle);

  oa_hostApi_Manager_resolve(..., refListHandle, ...);

  oa_EntityReferenceList_dtor(refListHandle);

This seems quite efficient, but has the disadvantage that we allow entity reference objects to be constructed independently from createEntityReference. But then C devs are supposed to be grown-ups.

A possible variation that enforces validation as items are added to the list would be, as suggested in the comment above, to use a dedicated oa_hostApi_Manager_addEntityReference function


oa_ErrorCode oa_hostApi_Manager_addEntityReference(oa_StringView* err, oa_hostApi_Manager_h handle,
                                                   oa_EntityReferenceList_h refListHandle,
                                                   oa_ConstStringView entityReferenceString) {
  return errors::catchUnknownExceptionAsCode(err, [&] {
    const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);

    std::optional<EntityReference> maybeEntityRef = manager->createEntityReferenceIfValid(
        {entityReferenceString.data, entityReferenceString.size});

    if (!maybeEntityRef) {
      openassetio::assignStringView(err, "Invalid entity reference");
      return oa_ErrorCode_kInvalidEntityReference;
    }
    
    std::vector<EntityReference>& refList = *handles::EntityReferenceList::toInstance(refListHandle);
    refList.push_back(std::move(*maybeEntityRef));

    return oa_ErrorCode_kOK;
  });
}

feltech avatar Aug 12 '22 10:08 feltech

C-Api needs more holistic thinking, out of date.

elliotcmorris avatar Mar 07 '24 15:03 elliotcmorris