PeerCache.java
/* Copyright 2022-2025 Thales Alenia Space
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You 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.
*/
package org.orekit.frames;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
/** Cache for frame transforms.
* <p>
* This class is thread-safe.
* </p>
* @author Luc Maisonobe
* @since 13.1
*/
class PeerCache {
/** Origin frame. */
private final Frame origin;
/** Cache for transforms with peer frame. */
private volatile CachedTransformProvider cache;
/** Lock for peer frame cache. */
private final ReentrantReadWriteLock lock;
/** Cache for transforms with peer frame. */
private volatile Map<Field<? extends CalculusFieldElement<?>>, FieldCachedTransformProvider<?>> fieldCaches;
/** create an instance not associated with any peer.
* @param origin origin frame
*/
PeerCache(final Frame origin) {
this.origin = origin;
this.cache = null;
this.fieldCaches = null;
this.lock = new ReentrantReadWriteLock();
}
/** Associate a cache with a peer frame, caching transforms.
* <p>
* The cache is a LRU cache (Least Recently Used), so entries remain in
* the cache if they are used frequently, and only older entries
* that have not been accessed for a while will be expunged.
* </p>
* <p>
* If a peer was already associated with this frame, it will be overridden.
* </p>
* <p>
* Peering is unidirectional, i.e. if frameA is peered with frameB,
* then frameB may be peered with another frameC or no frame at all.
* This allows several frames to be peered with a pivot one (typically
* Earth frame and many topocentric frames all peered with one inertial frame).
* </p>
* @param peer peer frame (if null, cache is cleared)
* @param cacheSize number of transforms kept in the date-based cache
*/
public void setPeerCaching(final Frame peer, final int cacheSize) {
lock.writeLock().lock();
try {
if (peer == null) {
// clear peering
cache = null;
fieldCaches = null;
}
// caching for regular dates
cache = createCache(peer, cacheSize);
// caching for field dates
fieldCaches = new ConcurrentHashMap<>();
} finally {
lock.writeLock().unlock();
}
}
/** Get the peer associated to this frame.
* @return peer associated with this frame, null if not peered at all
*/
Frame getPeer() {
lock.readLock().lock();
try {
return cache == null ? null : cache.getDestination();
} finally {
lock.readLock().unlock();
}
}
/** Get the cached transform provider associated with this destination.
* @param destination destination frame to which we want to transform vectors
* @return cached transform provider, or null if destination is not the instance peer
*/
CachedTransformProvider getCachedTransformProvider(final Frame destination) {
lock.readLock().lock();
try {
if (cache == null || cache.getDestination() != destination) {
return null;
} else {
return cache;
}
} finally {
lock.readLock().unlock();
}
}
/** Get the cached transform provider associated with this destination.
* @param <T> the type of the field elements
* @param destination destination frame to which we want to transform vectors
* @param field field elements belong to
* @return cached transform provider, or null if destination is not the instance peer
*/
@SuppressWarnings("unchecked")
<T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getCachedTransformProvider(final Frame destination,
final Field<T> field) {
lock.readLock().lock();
try {
if (cache == null || cache.getDestination() != destination) {
return null;
} else {
@SuppressWarnings("unchedked")
final FieldCachedTransformProvider<T> tp =
(FieldCachedTransformProvider<T>) fieldCaches.computeIfAbsent(field,
f -> createCache(destination,
cache.getCacheSize(),
field));
return tp;
}
} finally {
lock.readLock().unlock();
}
}
/** Create cache.
* @param peer peer frame
* @param cacheSize number of transforms kept in the date-based cache
* @return built cache
* @since 13.0.3
*/
private CachedTransformProvider createCache(final Frame peer, final int cacheSize) {
final Function<AbsoluteDate, Transform> fullGenerator =
date -> origin.getTransformTo(peer,
Transform.IDENTITY,
frame -> frame.getTransformProvider().getTransform(date),
(t1, t2) -> new Transform(date, t1, t2),
Transform::getInverse);
final Function<AbsoluteDate, KinematicTransform> kinematicGenerator =
date -> origin.getTransformTo(peer,
KinematicTransform.getIdentity(),
frame -> frame.getTransformProvider().getTransform(date),
(t1, t2) -> KinematicTransform.compose(date, t1, t2),
KinematicTransform::getInverse);
final Function<AbsoluteDate, StaticTransform> staticGenerator =
date -> origin.getTransformTo(peer,
StaticTransform.getIdentity(),
frame -> frame.getTransformProvider().getTransform(date),
(t1, t2) -> StaticTransform.compose(date, t1, t2),
StaticTransform::getInverse);
return new CachedTransformProvider(origin, peer,
fullGenerator, kinematicGenerator, staticGenerator,
cacheSize);
}
/** Create field cache.
* @param <T> type of the field elements
* @param peer peer frame
* @param cacheSize number of transforms kept in the date-based cache
* @param field field elements belong to
* @return built cache
* @since 13.0.3
*/
private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T>
createCache(final Frame peer, final int cacheSize, final Field<T> field) {
final Function<FieldAbsoluteDate<T>, FieldTransform<T>> fullGenerator =
d -> origin.getTransformTo(peer,
FieldTransform.getIdentity(field),
frame -> frame.getTransformProvider().getTransform(d),
(FieldTransform<T> t1, FieldTransform<T> t2) -> new FieldTransform<>(d, t1, t2),
FieldTransform::getInverse);
final Function<FieldAbsoluteDate<T>, FieldKinematicTransform<T>> kinematicGenerator =
d -> origin.getTransformTo(peer,
FieldKinematicTransform.getIdentity(field),
frame -> frame.getTransformProvider().getTransform(d),
(t1, t2) -> FieldKinematicTransform.compose(d, t1, t2),
FieldKinematicTransform::getInverse);
final Function<FieldAbsoluteDate<T>, FieldStaticTransform<T>> staticGenerator =
d -> origin.getTransformTo(peer,
FieldStaticTransform.getIdentity(field),
frame -> frame.getTransformProvider().getTransform(d),
(t1, t2) -> FieldStaticTransform.compose(d, t1, t2),
FieldStaticTransform::getInverse);
return new FieldCachedTransformProvider<>(origin, peer,
fullGenerator, kinematicGenerator, staticGenerator,
cacheSize);
}
}