Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/aether-objects/env/env.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2026 Aethernet Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef AETHER_OBJECTS_ENV_ENV_H_
#define AETHER_OBJECTS_ENV_ENV_H_

#include <cassert>
#include <concepts>
#include <cstdint>
#include <cstdlib>
#include <type_traits>

#include "aether-miscpp/meta/type_index.h"

namespace ae {

using EnvId = std::uint32_t;

static_assert(sizeof(EnvId) == 4);

/**
* @brief Customizes the identifier used to find a component in an Env.
*
* Specialize this trait for an unqualified component type when a stable custom
* identifier is required. The default is a CRC32 of GetTypeIndex<C>(). Its
* value depends on compiler-generated type names, so it is not portable across
* compilers and CRC32 collisions are possible. Env IDs are process-local
* lookup keys only and must not be persisted. Specializations must be ODR
* consistent throughout a program.
*/
template <typename T>
requires(!std::is_const_v<T> && !std::is_volatile_v<T> &&
!std::is_reference_v<T>)
struct EnvTypeId {
static constexpr EnvId value = static_cast<EnvId>(GetTypeIndex<T>());
};

/**
* \brief Provides lookup access to components in a concrete environment.
*/
class Env {
public:
virtual ~Env() = default;

/**
* \brief Returns the component of type `T`.
*
* The component must be available in this environment.
*/
template <typename T>
T& get() {
auto* component = try_get<T>();
assert(component != nullptr && "Env component is not available");
if (component == nullptr) {
std::abort();
}
return *component;
}

/**
* \brief Returns the component of type `T`, or `nullptr` when unavailable.
*/
template <typename T>
requires(!std::is_const_v<T> && !std::is_volatile_v<T> &&
!std::is_reference_v<T>)
auto try_get() noexcept -> T* {
return static_cast<T*>(find_component(EnvTypeId<T>::value));
}

protected:
/**
* \brief Finds the live component identified by @p id.
*
* Each concrete environment must support components with unique
* EnvTypeId values. The default values are CRC32 hashes of compiler-generated
* type names and can collide; custom IDs can collide as well. CRC32-based
* default IDs also are not portable across compilers. A collision makes
* component lookup ambiguous, so a concrete environment must choose unique
* IDs for all of its supported components.
*
* Implementations should compare @p id with EnvTypeId<T>::value and return
* the address of the corresponding live component, or nullptr for an
* unknown ID. For a lookup of `T`, the returned pointer must point to an
* object whose exact normalized type is `T` (without cv/ref qualifiers),
* rather than a base or related type. The object must remain alive and at a
* stable address for the entire time callers use the returned pointer.
*/
virtual void* find_component(EnvId id) noexcept = 0;
};

/**
* \brief Requires a provider that exposes an Env through get_env().
*/
template <typename P>
concept EnvProvider = requires(P const& provider) {
{ provider.get_env() } -> std::same_as<Env*>;
};

/**
* \brief Returns the component of type `T` from @p provider's environment.
*
* The provider's get_env() must return an environment, and that environment
* must provide the component. A null environment violates this contract and
* aborts the program.
*/
template <typename T>
auto get_env(EnvProvider auto const& provider) -> T& {
auto* env = provider.get_env();
assert(env != nullptr && "Env provider does not have an environment");
if (env == nullptr) {
std::abort();
}
return env->template get<T>();
}

/**
* \brief Returns the component of type `T` from @p provider's environment.
*
* Returns `nullptr` when the provider's get_env() returns `nullptr` or its
* environment does not provide the component.
*/
template <typename T>
auto try_get_env(EnvProvider auto const& provider) -> T*
{
auto* env = provider.get_env();
return env == nullptr ? nullptr : env->template try_get<T>();
}

} // namespace ae

#endif // AETHER_OBJECTS_ENV_ENV_H_
46 changes: 33 additions & 13 deletions src/aether-objects/obj/domain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,34 @@ namespace ae {
DomainGraph::DomainGraph(Domain* domain) : domain(domain) { assert(domain); }

Ptr<Obj> DomainGraph::LoadRoot(ObjId obj_id) {
if (!obj_id.is_valid()) {
Ptr<Obj> ptr;
if (LoadRoot(obj_id, ptr).IsErr()) {
return {};
}
return ptr;
}

seri::SeriResult DomainGraph::LoadRoot(ObjId obj_id, Ptr<Obj>& ptr) {
if (!obj_id.is_valid()) {
return Error{seri::read_error};
}
// if already loaded
if (auto obj = domain->Find(obj_id); obj) {
return obj;
if (ptr = domain->Find(obj_id); ptr) {
return Ok{seri::good};
}

auto* factory = domain->GetMostRelatedFactory(obj_id);
if (factory == nullptr) {
return {};
return Error{seri::read_error};
}

auto ptr = domain->ConstructObj(*factory, obj_id);
factory->load(this, ptr, obj_id);
return ptr;
ptr = domain->ConstructObj(*factory, obj_id);
auto result = factory->load(this, ptr, obj_id);
if (result && result->IsErr()) {
domain->RemoveObject(ptr.get());
ptr.Reset();
}
return result.value_or(Ok{seri::good});
}

Ptr<Obj> DomainGraph::LoadCopyImpl(ObjId ref_id, ObjId copy_id) {
Expand All @@ -62,18 +74,24 @@ Ptr<Obj> DomainGraph::LoadCopyImpl(ObjId ref_id, ObjId copy_id) {

auto ptr = domain->ConstructObj(*factory, copy_id);
// load new object with ref_id
factory->load(this, ptr, ref_id);
auto result = factory->load(this, ptr, ref_id);
if (result && result->IsErr()) {
domain->RemoveObject(ptr.get());
return {};
}
return ptr;
}

void DomainGraph::SaveRoot(Ptr<Obj> const& ptr, ObjId obj_id) {
seri::SeriResult DomainGraph::SaveRoot(Ptr<Obj> const& ptr, ObjId obj_id) {
if (!ptr) {
return;
return Error{seri::write_error};
}
if (auto* factory = domain->FindClassFactory(ptr->GetClassId());
factory != nullptr) {
factory->save(this, ptr, obj_id);
auto result = factory->save(this, ptr, obj_id);
return result.value_or(Ok{seri::good});
}
return Error{seri::write_error};
}

DomainLoad DomainGraph::GetReader(DomainQuery const& query) {
Expand All @@ -87,8 +105,10 @@ std::unique_ptr<IDomainStorageWriter> DomainGraph::GetWriter(
return writer;
}

Domain::Domain(IDomainStorage& storage)
: storage_{&storage}, registry_{&Registry::GetRegistry()} {}
Domain::Domain(IDomainStorage& storage) : Domain(storage, nullptr) {}

Domain::Domain(IDomainStorage& storage, Env* env)
: storage_{&storage}, registry_{&Registry::GetRegistry()}, env_{env} {}

Ptr<Obj> Domain::ConstructObj(Factory const& factory, ObjId obj_id) {
Ptr<Obj> o = factory.create();
Expand Down
64 changes: 48 additions & 16 deletions src/aether-objects/obj/domain.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,34 @@
#include <cassert>
#include <cstdint>
#include <map>
#include <optional>
#include <set>
#include <type_traits>
#include <utility>

#include "aether-miscpp/domain_visitor/domain_visitor.h"
#include "aether-miscpp/serialization/binary_archive.h"
#include "aether-miscpp/serialization/serialization.h"

#include "aether-objects/ptr/ptr_view.h"

#include "aether-objects/env/env.h"
#include "aether-objects/obj/idomain_storage.h"
#include "aether-objects/obj/obj_id.h"
#include "aether-objects/obj/registry.h"
#include "aether-objects/obj/version_iterator.h"

namespace ae {

namespace domain_internal {
inline constexpr seri::SeriError missing_version{
.error_code = 1000, .message = "Object version is missing"};
} // namespace domain_internal

class Obj;
class Domain;
class DomainGraph;
template <typename T>
class Registrar;

struct DomainCycleDetector {
struct Node {
Expand Down Expand Up @@ -104,21 +113,22 @@ class DomainGraph {

// Load saved state of object.
Ptr<Obj> LoadRoot(ObjId obj_id);
seri::SeriResult LoadRoot(ObjId obj_id, Ptr<Obj>& ptr);
// Save state of object.
void SaveRoot(Ptr<Obj> const& ptr, ObjId obj_id);
seri::SeriResult SaveRoot(Ptr<Obj> const& ptr, ObjId obj_id);
// Load a copy of object
template <typename T>
Ptr<T> LoadCopy(ObjId ref_id, ObjId copy_id);

Ptr<Obj> LoadCopyImpl(ObjId ref_id, ObjId copy_id);

template <typename T>
seri::SeriResult Load(T& obj, ObjId obj_id);
std::optional<seri::SeriResult> Load(T& obj, ObjId obj_id);
template <typename T, auto V>
seri::SeriResult LoadVersion(Version<V> version, T& obj, ObjId obj_id);

template <typename T>
seri::SeriResult Save(T const& obj, ObjId obj_id);
std::optional<seri::SeriResult> Save(T const& obj, ObjId obj_id);
template <typename T, auto V>
seri::SeriResult SaveVersion(Version<V> version, T const& obj, ObjId obj_id);

Expand All @@ -130,11 +140,20 @@ class DomainGraph {
std::unique_ptr<IDomainStorageWriter> GetWriter(DomainQuery const& query);
};

inline bool IsMissingVersion(seri::SeriResult const& result) {
return result.IsErr() && result.error().error_code ==
domain_internal::missing_version.error_code;
}

class Domain {
friend class DomainGraph;

public:
explicit Domain(IDomainStorage& storage);
Domain(IDomainStorage& storage, Env* env);

/// \brief Returns this domain's optional environment.
Env* get_env() const { return env_; }

// Search for the object by obj_id.
Ptr<Obj> Find(ObjId obj_id) const;
Expand All @@ -153,6 +172,7 @@ class Domain {

IDomainStorage* storage_;
Registry* registry_;
Env* env_{};

std::map<ObjId::Type, PtrView<Obj>> id_objects_;
};
Expand All @@ -163,19 +183,24 @@ Ptr<T> DomainGraph::LoadCopy(ObjId ref_id, ObjId copy_id) {
}

template <typename T>
seri::SeriResult DomainGraph::Load(T& obj, ObjId obj_id) {
std::optional<seri::SeriResult> DomainGraph::Load(T& obj, ObjId obj_id) {
if (!cycle_detector.Add(T::kClassId, obj_id)) {
return Ok{seri::good};
return std::nullopt;
}

if constexpr (HasAnyVersionedLoad<T>::value) {
auto result = seri::SeriResult{Ok{seri::good}};
auto result = seri::SeriResult{Error{domain_internal::missing_version}};
auto is_missing = true;
version_iterator<VersionLoadTrait>(
obj, [this, obj_id, &result](auto version, auto& obj) {
if (result.IsErr()) {
obj, [this, obj_id, &result, &is_missing](auto version, auto& obj) {
if (!is_missing && result.IsErr()) {
return;
}
result = this->LoadVersion(version, obj, obj_id);
auto version_result = this->LoadVersion(version, obj, obj_id);
is_missing = IsMissingVersion(version_result);
if (!is_missing) {
result = version_result;
}
});
return result;
} else {
Expand All @@ -198,8 +223,11 @@ template <typename T, auto V>
seri::SeriResult DomainGraph::LoadVersion(Version<V> version, T& obj,
ObjId obj_id) {
auto load = GetReader({obj_id, T::kClassId, V});
if (load.result != DomainLoadResult::kLoaded) {
return Ok{seri::good};
if (load.result == DomainLoadResult::kEmpty) {
return Error{domain_internal::missing_version};
}
if (load.result != DomainLoadResult::kLoaded || load.reader == nullptr) {
return Error{seri::read_error};
}

auto bin_archive =
Expand All @@ -222,9 +250,9 @@ seri::SeriResult DomainGraph::LoadVersion(Version<V> version, T& obj,
}

template <typename T>
seri::SeriResult DomainGraph::Save(T const& obj, ObjId obj_id) {
std::optional<seri::SeriResult> DomainGraph::Save(T const& obj, ObjId obj_id) {
if (!cycle_detector.Add(T::kClassId, obj_id)) {
return Ok{seri::good};
return std::nullopt;
}

if constexpr (HasAnyVersionedSave<T>::value) {
Expand Down Expand Up @@ -285,11 +313,15 @@ struct Serializer<BinaryArchive<DomainBuffer>, T> {
using Archive = BinaryArchive<DomainBuffer>;

SeriResult Seri(Archive& arch, Meta<T const> meta_val) const {
return arch.buffer().domain_graph->Save(meta_val.value, arch.buffer().id);
auto result =
arch.buffer().domain_graph->Save(meta_val.value, arch.buffer().id);
return result.value_or(Ok{good});
}

SeriResult Deseri(Archive& arch, Meta<T> meta_val) const {
return arch.buffer().domain_graph->Load(meta_val.value, arch.buffer().id);
auto result =
arch.buffer().domain_graph->Load(meta_val.value, arch.buffer().id);
return result.value_or(Ok{good});
}
};

Expand Down
Loading
Loading