1 /* Copyright 2022-2026 Thales Alenia Space
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.frames;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.Field;
21 import org.orekit.time.AbsoluteDate;
22 import org.orekit.time.FieldAbsoluteDate;
23
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.locks.StampedLock;
27 import java.util.function.Function;
28 import java.util.function.Supplier;
29
30 /** Cache for frame transforms.
31 * <p>
32 * This class is thread-safe.
33 * </p>
34 * @author Luc Maisonobe
35 * @since 13.1
36 */
37 class PeerCache {
38
39 /** Origin frame. */
40 private final Frame origin;
41
42 /** Cache for transforms with peer frame. */
43 private volatile CachedTransformProvider cache;
44
45 /** Lock for peer frame cache. */
46 private final StampedLock lock;
47
48 /** Cache for transforms with peer frame. */
49 private volatile Map<Field<? extends CalculusFieldElement<?>>, FieldCachedTransformProvider<?>> fieldCaches;
50
51 /** create an instance not associated with any peer.
52 * @param origin origin frame
53 */
54 PeerCache(final Frame origin) {
55 this.origin = origin;
56 this.cache = null;
57 this.fieldCaches = null;
58 this.lock = new StampedLock();
59 }
60
61 /** Associate a cache with a peer frame, caching transforms.
62 * <p>
63 * The cache is a LRU cache (Least Recently Used), so entries remain in
64 * the cache if they are used frequently, and only older entries
65 * that have not been accessed for a while will be expunged.
66 * </p>
67 * <p>
68 * If a peer was already associated with this frame, it will be overridden.
69 * </p>
70 * <p>
71 * Peering is unidirectional, i.e. if frameA is peered with frameB,
72 * then frameB may be peered with another frameC or no frame at all.
73 * This allows several frames to be peered with a pivot one (typically
74 * Earth frame and many topocentric frames all peered with one inertial frame).
75 * </p>
76 * @param peer peer frame (if null, cache is cleared)
77 * @param cacheSize number of transforms kept in the date-based cache
78 */
79 public void setPeerCaching(final Frame peer, final int cacheSize) {
80 final long lockId = lock.writeLock();
81 try {
82 if (peer == null) {
83 // clear peering
84 cache = null;
85 fieldCaches = null;
86 } else {
87 // caching for regular dates
88 cache = createCache(peer, cacheSize);
89 // caching for field dates
90 fieldCaches = new ConcurrentHashMap<>();
91 }
92 } finally {
93 lock.unlockWrite(lockId);
94 }
95 }
96
97 /** Get the peer associated to this frame.
98 * @return peer associated with this frame, null if not peered at all
99 */
100 Frame getPeer() {
101 if (cache == null) {
102 return null;
103 } else {
104 return optimisticRead(this::getPeerDestination);
105 }
106 }
107
108 private Frame getPeerDestination() {
109 return cache == null ? null : cache.getDestination();
110 }
111
112 /** Get the cached transform provider associated with this destination.
113 * @param destination destination frame to which we want to transform vectors
114 * @return cached transform provider, or null if destination is not the instance peer
115 */
116 CachedTransformProvider getCachedTransformProvider(final Frame destination) {
117 if (cache == null) {
118 return null;
119 } else {
120 return optimisticRead(() -> getProvider(destination));
121 }
122 }
123
124 private CachedTransformProvider getProvider(final Frame destination) {
125 final Frame peerDestination = getPeerDestination();
126 if (peerDestination == null || peerDestination != destination) {
127 return null;
128 } else {
129 return cache;
130 }
131 }
132
133 /** Get the cached transform provider associated with this destination.
134 * @param <T> the type of the field elements
135 * @param destination destination frame to which we want to transform vectors
136 * @param field field elements belong to
137 * @return cached transform provider, or null if destination is not the instance peer
138 */
139 <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getCachedTransformProvider(final Frame destination,
140 final Field<T> field) {
141 if (cache == null) {
142 return null;
143 } else {
144 return optimisticRead(() -> getFieldProvider(destination, field));
145 }
146 }
147
148 private <T> T optimisticRead(final Supplier<T> readFunction) {
149 final long optimisticLockId = lock.tryOptimisticRead();
150 final T optimisticResult = readFunction.get();
151 if (lock.validate(optimisticLockId)) {
152 return optimisticResult;
153 } else {
154 final long heavyLockId = lock.readLock();
155 try {
156 return readFunction.get();
157 } finally {
158 lock.unlockRead(heavyLockId);
159 }
160 }
161 }
162
163 @SuppressWarnings("unchecked")
164 private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getFieldProvider(final Frame destination, final Field<T> field) {
165 final CachedTransformProvider provider = getProvider(destination);
166 if (provider == null) {
167 return null;
168 } else {
169 return (FieldCachedTransformProvider<T>) fieldCaches.computeIfAbsent(field, f -> createCache(destination,
170 provider.getCacheSize(),
171 field));
172 }
173 }
174
175 /** Create cache.
176 * @param peer peer frame
177 * @param cacheSize number of transforms kept in the date-based cache
178 * @return built cache
179 * @since 13.0.3
180 */
181 private CachedTransformProvider createCache(final Frame peer, final int cacheSize) {
182 final Function<AbsoluteDate, Transform> fullGenerator =
183 date -> origin.getTransformTo(peer,
184 Transform.IDENTITY,
185 frame -> frame.getTransformProvider().getTransform(date),
186 (t1, t2) -> new Transform(date, t1, t2),
187 Transform::getInverse);
188 final Function<AbsoluteDate, KinematicTransform> kinematicGenerator =
189 date -> origin.getTransformTo(peer,
190 KinematicTransform.getIdentity(),
191 frame -> frame.getTransformProvider().getTransform(date),
192 (t1, t2) -> KinematicTransform.compose(date, t1, t2),
193 KinematicTransform::getInverse);
194 final Function<AbsoluteDate, StaticTransform> staticGenerator =
195 date -> origin.getTransformTo(peer,
196 StaticTransform.getIdentity(),
197 frame -> frame.getTransformProvider().getTransform(date),
198 (t1, t2) -> StaticTransform.compose(date, t1, t2),
199 StaticTransform::getInverse);
200 return new CachedTransformProvider(origin, peer,
201 fullGenerator, kinematicGenerator, staticGenerator,
202 cacheSize);
203 }
204
205 /** Create field cache.
206 * @param <T> type of the field elements
207 * @param peer peer frame
208 * @param cacheSize number of transforms kept in the date-based cache
209 * @param field field elements belong to
210 * @return built cache
211 * @since 13.0.3
212 */
213 private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T>
214 createCache(final Frame peer, final int cacheSize, final Field<T> field) {
215 final Function<FieldAbsoluteDate<T>, FieldTransform<T>> fullGenerator =
216 d -> origin.getTransformTo(peer,
217 FieldTransform.getIdentity(field),
218 frame -> frame.getTransformProvider().getTransform(d),
219 (FieldTransform<T> t1, FieldTransform<T> t2) -> new FieldTransform<>(d, t1, t2),
220 FieldTransform::getInverse);
221 final Function<FieldAbsoluteDate<T>, FieldKinematicTransform<T>> kinematicGenerator =
222 d -> origin.getTransformTo(peer,
223 FieldKinematicTransform.getIdentity(field),
224 frame -> frame.getTransformProvider().getTransform(d),
225 (t1, t2) -> FieldKinematicTransform.compose(d, t1, t2),
226 FieldKinematicTransform::getInverse);
227 final Function<FieldAbsoluteDate<T>, FieldStaticTransform<T>> staticGenerator =
228 d -> origin.getTransformTo(peer,
229 FieldStaticTransform.getIdentity(field),
230 frame -> frame.getTransformProvider().getTransform(d),
231 (t1, t2) -> FieldStaticTransform.compose(d, t1, t2),
232 FieldStaticTransform::getInverse);
233 return new FieldCachedTransformProvider<>(origin, peer,
234 fullGenerator, kinematicGenerator, staticGenerator,
235 cacheSize);
236 }
237
238 }