Files
EVEngine/src/modules/asset/EvpackRegistry.cpp
T

267 lines
13 KiB
C++

#include "asset/EvpackRegistry.h"
#include <limits>
#include <set>
#include <exception>
namespace eve::asset {
namespace {
template <class T>
Result<T> registryFailure(DiagnosticCode code, std::string message, std::string path = {}) {
return Result<T>::failure(Diagnostic::error(code, std::move(message), std::move(path), {},
"asset.evpack.registry"));
}
Result<void> registryFailureVoid(DiagnosticCode code, std::string message) {
return Result<void>::failure(Diagnostic::error(code, std::move(message), {}, {},
"asset.evpack.registry"));
}
template <class Entry>
bool sameGeneration(const EvpackHandle& handle, const Entry& entry) {
return handle.generation == entry.generation && handle.buildId == entry.pack->buildId();
}
std::vector<Diagnostic> dispatchCallbacks(
const std::vector<EvpackRegistry::Callback>& callbacks,
const EvpackRegistryEvent& event) {
std::vector<Diagnostic> diagnostics;
for (const auto& callback : callbacks) {
try {
auto notified = callback(event);
if (!notified && notified.error()) diagnostics.push_back(*notified.error());
} catch (const std::exception& error) {
diagnostics.push_back(Diagnostic::error(DiagnosticCode::CallbackFailure,
"registry callback threw: " + std::string(error.what()),
event.handle.packageId.format(), {},
"asset.evpack.registry"));
} catch (...) {
diagnostics.push_back(Diagnostic::error(DiagnosticCode::CallbackFailure,
"registry callback threw a non-standard exception",
event.handle.packageId.format(), {},
"asset.evpack.registry"));
}
}
return diagnostics;
}
} // namespace
Result<PreparedEvpackMount> prepareEvpackMount(std::span<const std::uint8_t> bytes,
const EvpackLimits& limits,
const EvpackTrust& trust) {
auto parsed = parseEvpack(bytes, limits, trust);
if (!parsed) return Result<PreparedEvpackMount>::failure(parsed.status());
auto pack = std::make_shared<const Evpack>(std::move(parsed).takeValue());
return Result<PreparedEvpackMount>::success(PreparedEvpackMount(std::move(pack)));
}
Result<EvpackRegistrySubscription> EvpackRegistry::subscribe(Callback callback) {
if (!callback)
return registryFailure<EvpackRegistrySubscription>(DiagnosticCode::InvalidArgument,
"registry callback is empty");
std::lock_guard lock(mutex_);
if (nextSubscription_ == std::numeric_limits<std::uint64_t>::max())
return registryFailure<EvpackRegistrySubscription>(DiagnosticCode::InvariantViolation,
"registry subscription identity is exhausted");
const auto id = ++nextSubscription_;
callbacks_.emplace(id, std::move(callback));
return Result<EvpackRegistrySubscription>::success({id});
}
Result<void> EvpackRegistry::unsubscribe(EvpackRegistrySubscription subscription) {
if (subscription.value == 0)
return registryFailureVoid(DiagnosticCode::InvalidArgument,
"registry subscription identity is invalid");
std::lock_guard lock(mutex_);
if (callbacks_.erase(subscription.value) == 0)
return registryFailureVoid(DiagnosticCode::NotFound,
"registry subscription is not active");
return Result<void>::success();
}
Result<EvpackMountReceipt> EvpackRegistry::commit(PreparedEvpackMount candidate) {
if (!candidate.pack_)
return registryFailure<EvpackMountReceipt>(DiagnosticCode::InvalidArgument,
"mount candidate has already been consumed");
const PersistentId packageId = candidate.pack_->packageId();
bool replaced = false;
EvpackHandle handle;
std::vector<Callback> callbacks;
{
std::lock_guard lock(mutex_);
std::set<PersistentId> available;
std::set<PersistentId> candidateAssets;
for (const auto& chunk : candidate.pack_->chunks()) {
available.emplace(chunk.assetId);
candidateAssets.emplace(chunk.assetId);
}
for (const auto& [mountedId, entry] : packages_) {
if (mountedId == packageId) continue;
for (const auto& chunk : entry.pack->chunks()) {
if (candidateAssets.contains(chunk.assetId))
return registryFailure<EvpackMountReceipt>(
DiagnosticCode::Conflict,
"runtime asset identity already has another mounted provider",
chunk.assetId.format());
available.emplace(chunk.assetId);
}
}
for (const auto& chunk : candidate.pack_->chunks()) {
for (const auto& dependency : chunk.dependencies) {
if (!available.contains(dependency))
return registryFailure<EvpackMountReceipt>(
DiagnosticCode::NotFound, "required runtime dependency is not mounted",
dependency.format());
}
}
for (const auto& [mountedId, entry] : packages_) {
if (mountedId == packageId) continue;
for (const auto& chunk : entry.pack->chunks()) {
for (const auto& dependency : chunk.dependencies) {
if (!available.contains(dependency))
return registryFailure<EvpackMountReceipt>(
DiagnosticCode::NotFound,
"replacement would invalidate a mounted package dependency",
dependency.format());
}
}
}
replaced = packages_.contains(packageId);
std::uint64_t& next = nextGenerations_[packageId];
if (next == std::numeric_limits<std::uint64_t>::max())
return registryFailure<EvpackMountReceipt>(DiagnosticCode::InvariantViolation,
"package generation is exhausted", packageId.format());
++next;
auto pack = std::move(candidate.pack_);
handle = {packageId, pack->buildId(), next};
packages_.insert_or_assign(packageId, Entry{std::move(pack), next});
callbacks.reserve(callbacks_.size());
for (const auto& [id, callback] : callbacks_) {
(void)id;
callbacks.push_back(callback);
}
}
const EvpackRegistryEvent event{replaced ? EvpackRegistryEventKind::Replaced
: EvpackRegistryEventKind::Mounted,
handle};
auto callbackDiagnostics = dispatchCallbacks(callbacks, event);
return Result<EvpackMountReceipt>::success({handle, replaced, std::move(callbackDiagnostics)});
}
Result<std::shared_ptr<const Evpack>> EvpackRegistry::resolve(const EvpackHandle& handle) const {
std::lock_guard lock(mutex_);
const auto found = packages_.find(handle.packageId);
if (found == packages_.end())
return registryFailure<std::shared_ptr<const Evpack>>(DiagnosticCode::NotFound,
"runtime package is not mounted",
handle.packageId.format());
if (!sameGeneration(handle, found->second))
return registryFailure<std::shared_ptr<const Evpack>>(DiagnosticCode::StaleHandle,
"runtime package handle is stale",
handle.packageId.format());
return Result<std::shared_ptr<const Evpack>>::success(found->second.pack);
}
Result<EvpackAssetHandle> EvpackRegistry::resolveAsset(
const AssetRef& assetRef, std::string_view expectedType,
const EvpackCapabilities& capabilities) const {
if (expectedType.empty())
return registryFailure<EvpackAssetHandle>(DiagnosticCode::InvalidArgument,
"expected runtime asset type is empty");
std::lock_guard lock(mutex_);
bool identityFound = false;
bool typeFound = false;
for (const auto& [packageId, entry] : packages_) {
const EvpackChunk* representative = nullptr;
for (const auto& chunk : entry.pack->chunks()) {
if (chunk.assetId != assetRef.id()) continue;
identityFound = true;
representative = &chunk;
const std::string actual = chunk.type + "/" +
std::to_string(chunk.schemaVersion.value());
if (actual == expectedType) typeFound = true;
else
return registryFailure<EvpackAssetHandle>(
DiagnosticCode::TypeMismatch,
"runtime asset provider has a different canonical type", assetRef.format());
}
if (!representative) continue;
auto selected = selectEvpackVariant(*entry.pack, capabilities);
if (!selected) continue;
return Result<EvpackAssetHandle>::success(
{{packageId, entry.pack->buildId(), entry.generation}, assetRef,
std::string(expectedType), capabilities});
}
if (identityFound && typeFound)
return registryFailure<EvpackAssetHandle>(DiagnosticCode::Unsupported,
"asset provider has no compatible variant",
assetRef.format());
return registryFailure<EvpackAssetHandle>(DiagnosticCode::NotFound,
"runtime asset has no mounted provider",
assetRef.format());
}
Result<RuntimeAssetPayload> EvpackRegistry::readAsset(
const EvpackAssetHandle& handle, std::uint64_t maximumDecodedBytes) const {
auto pack = resolve(handle.package);
if (!pack) return Result<RuntimeAssetPayload>::failure(pack.status());
EvpackResourceReader reader(std::move(pack).takeValue());
return reader.read(handle.asset, handle.expectedType, handle.capabilities,
maximumDecodedBytes);
}
Result<EvpackChunkHandle> EvpackRegistry::resolveChunk(const EvpackHandle& handle,
const PersistentId& assetId, std::string_view type,
EvpackChunkKind kind,
std::uint32_t chunkId,
std::uint32_t variantIndex) const {
auto resolved = resolve(handle);
if (!resolved) return Result<EvpackChunkHandle>::failure(resolved.status());
const auto pack = std::move(resolved).takeValue();
for (std::size_t index = 0; index < pack->chunks().size(); ++index) {
const auto& chunk = pack->chunks()[index];
if (chunk.assetId == assetId && chunk.type == type && chunk.kind == kind && chunk.chunkId == chunkId &&
chunk.variantIndex == variantIndex)
return Result<EvpackChunkHandle>::success({handle, static_cast<std::uint32_t>(index)});
}
return registryFailure<EvpackChunkHandle>(DiagnosticCode::NotFound, "runtime chunk is not present",
assetId.format());
}
Result<std::vector<std::uint8_t>> EvpackRegistry::copyChunkBytes(const EvpackChunkHandle& handle) const {
auto resolved = resolve(handle.package);
if (!resolved) return Result<std::vector<std::uint8_t>>::failure(resolved.status());
const auto pack = std::move(resolved).takeValue();
if (handle.chunkIndex >= pack->chunks().size())
return registryFailure<std::vector<std::uint8_t>>(DiagnosticCode::InvalidArgument,
"chunk index is outside the TOC");
return pack->decodeChunk(handle.chunkIndex, pack->chunks()[handle.chunkIndex].decodedSize);
}
Result<EvpackUnmountReceipt> EvpackRegistry::unmount(const EvpackHandle& handle) {
std::vector<Callback> callbacks;
{
std::lock_guard lock(mutex_);
const auto found = packages_.find(handle.packageId);
if (found == packages_.end())
return registryFailure<EvpackUnmountReceipt>(DiagnosticCode::NotFound,
"runtime package is not mounted");
if (!sameGeneration(handle, found->second))
return registryFailure<EvpackUnmountReceipt>(DiagnosticCode::StaleHandle,
"runtime package handle is stale");
packages_.erase(found);
callbacks.reserve(callbacks_.size());
for (const auto& [id, callback] : callbacks_) {
(void)id;
callbacks.push_back(callback);
}
}
const EvpackRegistryEvent event{EvpackRegistryEventKind::Unmounted, handle};
auto callbackDiagnostics = dispatchCallbacks(callbacks, event);
return Result<EvpackUnmountReceipt>::success({handle, std::move(callbackDiagnostics)});
}
} // namespace eve::asset