3030import com .vaadin .flow .signals .Signal ;
3131import com .vaadin .flow .signals .SignalCommand ;
3232import com .vaadin .flow .signals .function .EffectAction ;
33- import com .vaadin .flow .signals .function .SignalComputation ;
3433import com .vaadin .flow .signals .impl .UsageTracker .Usage ;
3534import com .vaadin .flow .signals .shared .AbstractSharedSignal ;
3635import com .vaadin .flow .signals .shared .SharedNodeSignal ;
3736import com .vaadin .flow .signals .shared .impl .SynchronousSignalTree ;
3837
3938/**
40- * A signal with a value that is computed based on the value of other signals.
41- * The signal value will be lazily re-computed when needed after the value has
42- * changed for any of the signals that were used when computing the previous
43- * value. If the computation callback throws a {@link RuntimeException}, then
44- * that exception will be re-thrown when accessing the value of this signal. An
45- * {@link Signal#unboundEffect(EffectAction) effect} or computed signal that
46- * uses the value from a computed signal will not be invalidated if the
47- * computation is run again but produces the same value as before.
39+ * A signal that caches the value of an inner signal. The inner signal is
40+ * typically a computed signal performing an expensive computation that should
41+ * be run again only if any of its dependencies has changed.
42+ * <p>
43+ * If a {@link RuntimeException} is thrown when getting the value of the inner
44+ * signal, then that exception will be re-thrown when accessing the value of
45+ * this signal. An {@link Signal#unboundEffect(EffectAction) effect} or outer
46+ * cached signal that uses the value from a cached signal will not be
47+ * invalidated if the computation is run again but produces the same value as
48+ * before.
4849 *
4950 * @param <T>
5051 * the value type
5152 */
52- public class ComputedSignal <T extends @ Nullable Object >
53+ public class CachedSignal <T extends @ Nullable Object >
5354 extends AbstractSharedSignal <T > {
5455
5556 /*
5657 * This state is never supposed to be synchronized across a cluster or to
5758 * Hilla clients.
5859 */
59- private record ComputedState (@ Nullable Object value ,
60+ private record CacheState (@ Nullable Object value ,
6061 @ Nullable RuntimeException exception ,
6162 Usage dependencies ) implements Serializable {
6263 }
6364
64- private final SignalComputation <T > computation ;
65+ private final Signal <T > inner ;
6566
6667 private int dependentCount = 0 ;
6768 private @ Nullable Registration dependencyRegistration ;
6869
6970 /**
70- * Creates a new computed signal with the provided compute callback .
71+ * Creates a new cached signal with the provided inner signal .
7172 *
72- * @param computation
73- * a callback that returns the computed value , not null
73+ * @param inner
74+ * a signal to wrap , not null
7475 */
75- public ComputedSignal ( SignalComputation <T > computation ) {
76+ public CachedSignal ( Signal <T > inner ) {
7677 super (new SynchronousSignalTree (true ), Id .ZERO , ANYTHING_GOES );
77- this .computation = Objects .requireNonNull (computation );
78+ this .inner = Objects .requireNonNull (inner );
7879 }
7980
8081 /**
81- * As long as nobody listens to changes to this computed signal, we can just
82- * re-compute on demand every time the value is read. But when there's at
82+ * As long as nobody listens to changes to this cached signal, we can just
83+ * re-validate on demand every time the value is read. But when there's at
8384 * least one active listener, we need to actively listen to changes in our
8485 * dependencies.
8586 * <p>
8687 * Whenever a dependency changes, we run {@link #getValidState(Data)}. This
87- * causes the compute callback to run again. If that leads to a new value in
88+ * causes the inner signal to be read again. If that leads to a new value in
8889 * the tree, then the {@link Usage} from the super class will be triggered
8990 * which in notifies out external listeners.
9091 * <p>
9192 * We have just a single set of internal listeners on our dependencies even
92- * if there are multiple external listeners so that the compute callback is
93- * run only once. We keep track of how many active external listeners we
94- * have so that our internal listener is active if and only if there's at
95- * least one active external listener.
93+ * if there are multiple external listeners so that the inner signal is read
94+ * only once. We keep track of how many active external listeners we have so
95+ * that our internal listener is active if and only if there's at least one
96+ * active external listener.
9697 */
9798 private synchronized void revalidateAndListen () {
9899 // Clear listeners on old dependencies
99100 if (dependencyRegistration != null ) {
100101 dependencyRegistration .remove ();
101102 }
102103
103- // Run compute callback to get new dependencies
104- ComputedState state = getValidState (data (Transaction .getCurrent ()));
104+ // Read inner value to get new dependencies
105+ CacheState state = getValidState (data (Transaction .getCurrent ()));
105106
106107 // avoid lambda to allow proper deserialization
107108 TransientListener usageListener = new TransientListener () {
@@ -131,7 +132,7 @@ private synchronized Registration countActiveExternalListener() {
131132
132133 return () -> {
133134 if (!removed .getAndSet (true )) {
134- synchronized (ComputedSignal .this ) {
135+ synchronized (CachedSignal .this ) {
135136 /*
136137 * Decrease the number of active external listeners and stop
137138 * listening to our dependencies if there are no more
@@ -194,38 +195,38 @@ public boolean invoke(boolean immediate) {
194195 };
195196 }
196197
197- private ComputedState getValidState (@ Nullable Data data ) {
198- ComputedState state = readState (data );
198+ private CacheState getValidState (@ Nullable Data data ) {
199+ CacheState state = readState (data );
199200
200201 if (state == null || state .dependencies .hasChanges ()) {
201202 @ Nullable
202203 Object [] holder = new @ Nullable Object [2 ];
203204 Usage dependencies = UsageTracker .track (() -> {
204205 try {
205- holder [0 ] = computation . compute ();
206+ holder [0 ] = inner . get ();
206207 } catch (RuntimeException e ) {
207208 holder [1 ] = e ;
208209 }
209210 });
210211 if (dependencies == UsageTracker .NO_USAGE ) {
211212 throw new MissingSignalUsageException (
212- "Signal computation must read at least one signal value." );
213+ "A computing inner signal must read at least one other signal value." );
213214 }
214215 @ Nullable
215216 Object value = holder [0 ];
216217 @ Nullable
217218 RuntimeException exception = (RuntimeException ) holder [1 ];
218219
219- state = new ComputedState (value , exception , dependencies );
220+ state = new CacheState (value , exception , dependencies );
220221
221222 submit (new SignalCommand .SetCommand (Id .random (), id (),
222- new ComputedPOJONode (state )));
223+ new CachedPOJONode (state )));
223224 }
224225
225226 return state ;
226227 }
227228
228- private static @ Nullable ComputedState readState (@ Nullable Data data ) {
229+ private static @ Nullable CacheState readState (@ Nullable Data data ) {
229230 if (data == null ) {
230231 return null ;
231232 }
@@ -238,22 +239,22 @@ private ComputedState getValidState(@Nullable Data data) {
238239 return extractState (value );
239240 }
240241
241- private static ComputedState extractState (JsonNode json ) {
242- ComputedPOJONode pojoNode = (ComputedPOJONode ) json ;
243- return (ComputedState ) pojoNode .getPojo ();
242+ private static CacheState extractState (JsonNode json ) {
243+ CachedPOJONode pojoNode = (CachedPOJONode ) json ;
244+ return (CacheState ) pojoNode .getPojo ();
244245 }
245246
246- private static class ComputedPOJONode extends POJONode
247+ private static class CachedPOJONode extends POJONode
247248 implements Serializable {
248249
249- public ComputedPOJONode (Object v ) {
250+ public CachedPOJONode (Object v ) {
250251 super (v );
251252 }
252253 }
253254
254255 @ Override
255256 protected @ Nullable T extractValue (@ Nullable Data data ) {
256- ComputedState state = getValidState (data );
257+ CacheState state = getValidState (data );
257258
258259 if (state .exception != null ) {
259260 throw state .exception ;
@@ -275,13 +276,12 @@ public ComputedPOJONode(Object v) {
275276
276277 @ Override
277278 public T peekConfirmed () {
278- throw new UnsupportedOperationException (
279- "Cannot peek a computed signal" );
279+ throw new UnsupportedOperationException ("Cannot peek a cached signal" );
280280 }
281281
282282 @ Override
283283 public SharedNodeSignal asNode () {
284284 throw new UnsupportedOperationException (
285- "Cannot use a computed signal as a node signal" );
285+ "Cannot use a cached signal as a node signal" );
286286 }
287287}
0 commit comments