Skip to content
Draft
4 changes: 4 additions & 0 deletions Core/include/Acts/Geometry/CompositePortalLink.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ class CompositePortalLink final : public PortalLinkBase {
/// @return The range of children
PortalLinkRange links() const;

/// Get the merge direction used to build this composite.
/// @return The merge direction
AxisDirection direction() const { return m_direction; }

private:
boost::container::small_vector<std::unique_ptr<PortalLinkBase>, 4>
m_children{};
Expand Down
1 change: 1 addition & 0 deletions Plugins/Json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ acts_add_library(
src/DetrayJsonHelper.cpp
src/JsonDetectorElement.cpp
src/JsonSurfacesReader.cpp
src/TrackingGeometryJsonConverter.cpp
src/DefinitionsJsonConverter.cpp
src/Seeding2ConfigJsonConverter.cpp
ACTS_INCLUDE_FOLDER include/ActsPlugins
Expand Down
97 changes: 97 additions & 0 deletions Plugins/Json/include/ActsPlugins/Json/JsonKindDispatcher.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include <cstddef>
#include <functional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <utility>

#include <nlohmann/json.hpp>

namespace Acts {

/// JSON-specific dispatcher that routes decoder functions based on a string
/// kind tag found in the encoded payload.
template <typename return_t, typename... args_t>
class JsonKindDispatcher {
public:
using decoder_type = std::function<return_t(const nlohmann::json&, args_t...)>;
using self_type = JsonKindDispatcher<return_t, args_t...>;

explicit JsonKindDispatcher(std::string kindKey = "kind",
std::string context = "JSON payload")
: m_kindKey(std::move(kindKey)), m_context(std::move(context)) {
if (m_kindKey.empty()) {
throw std::invalid_argument("JsonKindDispatcher kind key must be non-empty");
}
if (m_context.empty()) {
m_context = "JSON payload";
}
}

self_type& registerKind(std::string kind, decoder_type decoder) {
if (kind.empty()) {
throw std::invalid_argument("JsonKindDispatcher kind must be non-empty");
}
if (!decoder) {
throw std::invalid_argument("JsonKindDispatcher decoder must be valid");
}
auto [_, inserted] = m_decoders.emplace(std::move(kind), std::move(decoder));
if (!inserted) {
throw std::invalid_argument("JsonKindDispatcher duplicate kind registration");
}
return *this;
}

return_t operator()(const nlohmann::json& encoded, args_t... args) const {
if (!encoded.contains(m_kindKey)) {
throw std::invalid_argument("Missing '" + m_kindKey + "' in " + m_context);
}

const auto& kindValue = encoded.at(m_kindKey);
if (!kindValue.is_string()) {
throw std::invalid_argument("Invalid '" + m_kindKey + "' type in " +
m_context);
}

const auto kind = kindValue.template get<std::string>();
auto decoder = m_decoders.find(kind);
if (decoder == m_decoders.end()) {
throw std::invalid_argument("Unsupported " + m_context + " kind: " +
kind);
}

if constexpr (std::is_void_v<return_t>) {
decoder->second(encoded, std::forward<args_t>(args)...);
return;
} else {
return decoder->second(encoded, std::forward<args_t>(args)...);
}
}

bool hasKind(std::string_view kind) const {
return m_decoders.find(std::string{kind}) != m_decoders.end();
}

void clear() { m_decoders.clear(); }

std::size_t size() const { return m_decoders.size(); }

private:
std::string m_kindKey;
std::string m_context;
std::unordered_map<std::string, decoder_type> m_decoders;
};

} // namespace Acts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include "Acts/Geometry/GeometryContext.hpp"
#include "Acts/Utilities/TypeDispatcher.hpp"
#include "ActsPlugins/Json/JsonKindDispatcher.hpp"

#include <cstddef>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>

#include <nlohmann/json.hpp>

namespace Acts {

class PortalLinkBase;
class Portal;
class TrackingGeometry;
class TrackingVolume;
class VolumeBounds;

/// @addtogroup json_plugin
/// @{

/// Converter for tracking geometry JSON payloads focused on volumes, volume
/// bounds and portals.
///
/// High-level conversion overview:
/// - Serialization:
/// - traverse the `TrackingVolume::volumes()` tree in depth-first order
/// - assign stable in-file volume IDs
/// - collect unique portals and assign stable in-file portal IDs
/// - write each volume transform, bounds payload, children IDs, and portal IDs
/// - write all unique portals once in a top-level portal table
/// - encode portal links by concrete kind via registered dispatchers
/// - Deserialization:
/// - validate schema header and collect all volume records
/// - instantiate all volumes first and build ID->pointer lookup
/// - attach child volumes to reconstruct the tree
/// - decode unique portals by kind, then attach shared portal pointers to
/// volumes via portal IDs
/// - return a reconstructed world `TrackingVolume` (or `TrackingGeometry`)
class TrackingGeometryJsonConverter {
public:
/// JSON serialization options for tracking geometry conversion.
struct Options {};

/// Generic lookup from object pointer identity to serialized object ID.
template <typename object_t, const char* kContext>
struct PointerToIdLookup {
/// Insert a new object to ID mapping.
///
/// @param object is the source object pointer key
/// @param objectId is the serialized ID to assign
///
/// @return true if insertion happened, false if the object was already
/// present
bool emplace(const object_t& object, std::size_t objectId) {
return m_objectIds.emplace(&object, objectId).second;
}

/// Resolve a serialized object ID from an object reference.
///
/// @param object is the source object key
///
/// @return associated serialized object ID
///
/// @throw std::invalid_argument if the object is not in the lookup
std::size_t at(const object_t& object) const {
auto it = m_objectIds.find(&object);
if (it == m_objectIds.end()) {
throw std::invalid_argument(
"Pointer-to-ID lookup failed for " + std::string{kContext} +
": object is outside serialized hierarchy");
}
return it->second;
}

private:
std::unordered_map<const object_t*, std::size_t> m_objectIds;
};

/// Generic lookup from serialized ID to pointer-like object holder.
///
/// `pointer_t` can be a raw pointer (`object_t*`) or an owning pointer-like
/// type such as `std::shared_ptr<object_t>`.
template <typename object_t, typename pointer_t, const char* kContext>
struct IdToPointerLikeLookup {
/// Insert a new serialized ID to object mapping.
///
/// @param objectId is the serialized ID key
/// @param object is the target pointer-like object
///
/// @return true if insertion happened, false if the ID was already present
bool emplace(std::size_t objectId, pointer_t object) {
return m_objects.emplace(objectId, std::move(object)).second;
}

/// Try to find a mapped pointer-like object by serialized ID.
///
/// @param objectId is the serialized ID key
///
/// @return mapped pointer-like object, or null-equivalent if not found
pointer_t find(std::size_t objectId) const {
auto it = m_objects.find(objectId);
return it == m_objects.end() ? pointer_t{} : it->second;
}

/// Resolve a mapped pointer-like object by serialized ID.
///
/// @param objectId is the serialized ID key
///
/// @return mapped pointer-like object reference
///
/// @throw std::invalid_argument if the ID is not mapped
const pointer_t& at(std::size_t objectId) const {
auto it = m_objects.find(objectId);
if (it == m_objects.end()) {
throw std::invalid_argument(
"ID-to-pointer lookup failed for " + std::string{kContext} +
": unknown serialized object ID");
}
return it->second;
}

private:
std::unordered_map<std::size_t, pointer_t> m_objects;
};

static inline constexpr char kVolumeLookupContext[] = "volume";
static inline constexpr char kPortalLookupContext[] = "portal";

using VolumeIdLookup =
PointerToIdLookup<TrackingVolume, kVolumeLookupContext>;
using VolumePointerLookup = IdToPointerLikeLookup<
TrackingVolume, TrackingVolume*, kVolumeLookupContext>;
using PortalIdLookup = PointerToIdLookup<Portal, kPortalLookupContext>;
using PortalPointerLookup = IdToPointerLikeLookup<
Portal, std::shared_ptr<Portal>, kPortalLookupContext>;

using VolumeBoundsEncoder =
TypeDispatcher<VolumeBounds, nlohmann::json()>;

using PortalLinkEncoder = TypeDispatcher<
PortalLinkBase,
nlohmann::json(const GeometryContext&,
const TrackingGeometryJsonConverter&,
const VolumeIdLookup&)>;

using VolumeBoundsDecoder =
JsonKindDispatcher<std::unique_ptr<VolumeBounds>>;

using PortalLinkDecoder =
JsonKindDispatcher<std::unique_ptr<PortalLinkBase>,
const GeometryContext&,
const TrackingGeometryJsonConverter&,
const VolumePointerLookup&>;

/// Configuration for the tracking geometry JSON converter.
struct Config {
/// Dispatcher for volume bounds serialization.
VolumeBoundsEncoder encodeVolumeBounds{};

/// Dispatcher for portal link serialization.
PortalLinkEncoder encodePortalLink{};

/// Decoder dispatcher for volume bounds by kind tag.
VolumeBoundsDecoder decodeVolumeBounds{"kind", "volume bounds"};

/// Decoder dispatcher for portal links by kind tag.
PortalLinkDecoder decodePortalLink{"kind", "portal link"};

/// Construct default config with all supported converters registered.
static Config defaultConfig();
};

/// Construct converter with custom or default dispatch configuration.
///
/// @param config is the conversion dispatch configuration
explicit TrackingGeometryJsonConverter(Config config = Config::defaultConfig());

/// Convert a tracking geometry to JSON.
nlohmann::json toJson(const GeometryContext& gctx,
const TrackingGeometry& geometry,
const Options& options = Options{}) const;

/// Convert a tracking volume hierarchy to JSON.
nlohmann::json toJson(const GeometryContext& gctx,
const TrackingVolume& world,
const Options& options = Options{}) const;

/// Reconstruct a tracking volume hierarchy from JSON.
std::shared_ptr<TrackingVolume> trackingVolumeFromJson(
const GeometryContext& gctx, const nlohmann::json& encoded,
const Options& options = Options{}) const;

/// Reconstruct a tracking geometry from JSON.
std::shared_ptr<TrackingGeometry> trackingGeometryFromJson(
const GeometryContext& gctx, const nlohmann::json& encoded,
const Options& options = Options{}) const;

/// Serialize one portal link using the configured dispatcher.
nlohmann::json portalLinkToJson(const GeometryContext& gctx,
const PortalLinkBase& link,
const VolumeIdLookup& volumeIds) const;

/// Deserialize one portal link using configured decoders.
std::unique_ptr<PortalLinkBase> portalLinkFromJson(
const GeometryContext& gctx, const nlohmann::json& encoded,
const VolumePointerLookup& volumes) const;

private:
Config m_cfg;
};

/// @}
} // namespace Acts
Loading
Loading