@@ -2,26 +2,38 @@ module Specular.Internal.Incremental where
2
2
3
3
import Prelude
4
4
5
+ import Data.Either (Either (..))
5
6
import Data.Function.Uncurried (Fn2 , runFn2 )
7
+ import Data.Generic.Rep (class Generic )
8
+ import Data.Maybe (Maybe (..))
9
+ import Data.Show.Generic (genericShow )
6
10
import Effect (Effect )
11
+ import Effect.Aff (Aff , Error )
12
+ import Effect.Class (liftEffect )
7
13
import Effect.Console as Console
8
14
import Effect.Uncurried (EffectFn1 , EffectFn2 , EffectFn3 , mkEffectFn1 , mkEffectFn2 , mkEffectFn3 , runEffectFn1 , runEffectFn2 , runEffectFn3 , runEffectFn4 )
9
15
import Effect.Unsafe (unsafePerformEffect )
10
16
import Partial.Unsafe (unsafeCrashWith )
17
+ import Specular.Internal.ExclusiveTask as ExclusiveTask
18
+ import Specular.Internal.Effect (nextMicrotask )
11
19
import Specular.Internal.Incremental.Array as Array
12
20
import Specular.Internal.Incremental.Effect (foreachUntil )
13
- import Specular.Internal.Incremental.Global (globalCurrentStabilizationNum , globalTotalRefcount , globalLastStabilizationNum , stabilizationIsNotInProgress )
21
+ import Specular.Internal.Incremental.Global (globalCurrentStabilizationNum , globalLastStabilizationNum , globalTotalRefcount , stabilizationIsNotInProgress )
14
22
import Specular.Internal.Incremental.Mutable (Field (..))
15
23
import Specular.Internal.Incremental.MutableArray as MutableArray
16
- import Specular.Internal.Incremental.Node (Node , SomeNode , Observer , toSomeNode , toSomeNodeArray )
24
+ import Specular.Internal.Incremental.Node (Node , Observer , SomeNode , toSomeNode , toSomeNodeArray )
17
25
import Specular.Internal.Incremental.Node as Node
18
26
import Specular.Internal.Incremental.Optional (Optional )
19
27
import Specular.Internal.Incremental.Optional as Optional
20
28
import Specular.Internal.Incremental.PriorityQueue as PQ
21
29
import Specular.Internal.Incremental.Ref as Ref
22
30
import Specular.Internal.Profiling as Profiling
31
+ import Specular.Internal.Queue (Queue )
32
+ import Specular.Internal.Queue as Queue
23
33
import Unsafe.Coerce (unsafeCoerce )
24
34
35
+ type Unsubscribe = Effect Unit
36
+
25
37
-- | Priority queue for propagating node changes in dependency order.
26
38
globalRecomputeQueue :: PQ.PQ SomeNode
27
39
globalRecomputeQueue = unsafePerformEffect $
@@ -49,6 +61,10 @@ newVar = mkEffectFn1 \val -> do
49
61
setVar :: forall a . EffectFn2 (Var a ) a Unit
50
62
setVar = mkEffectFn2 \(Var node) val -> do
51
63
runEffectFn2 Node .set_value node (Optional .some val)
64
+ runEffectFn1 addToRecomputeQueue node
65
+
66
+ addToRecomputeQueue :: forall a . EffectFn1 (Node a ) Unit
67
+ addToRecomputeQueue = mkEffectFn1 \node -> do
52
68
_ <- runEffectFn2 PQ .add globalRecomputeQueue (toSomeNode node)
53
69
pure unit
54
70
@@ -175,12 +191,30 @@ disconnect = mkEffectFn1 \node -> do
175
191
176
192
runEffectFn1 Profiling .end mark
177
193
194
+ -- * Effect queue
195
+
196
+ globalEffectQueue :: Queue (Effect Unit )
197
+ globalEffectQueue = unsafePerformEffect Queue .new
198
+
199
+ subscribeNode :: forall a . EffectFn2 (a -> Effect Unit ) (Node a ) Unsubscribe
200
+ subscribeNode = mkEffectFn2 \handler node -> do
201
+ let
202
+ h = mkEffectFn1 \value -> do
203
+ runEffectFn2 Queue .enqueue globalEffectQueue (handler value)
204
+ runEffectFn2 addObserver node h
205
+ pure (runEffectFn2 removeObserver node h)
206
+
178
207
-- * Recompute
179
208
180
209
stabilize :: Effect Unit
181
210
stabilize = do
182
211
mark <- runEffectFn1 Profiling .begin " stabilize"
183
212
213
+ stabilizationNum <- runEffectFn1 Ref .read globalCurrentStabilizationNum
214
+ if stabilizationNum /= stabilizationIsNotInProgress then
215
+ unsafeCrashWith " Specular: stabilize called when stabilization already in progress"
216
+ else pure unit
217
+
184
218
oldStabilizationNum <- runEffectFn1 Ref .read globalLastStabilizationNum
185
219
let currentStabilizationNum = oldStabilizationNum + 1
186
220
runEffectFn2 Ref .write globalLastStabilizationNum currentStabilizationNum
@@ -191,6 +225,10 @@ stabilize = do
191
225
runEffectFn2 Ref .write globalCurrentStabilizationNum stabilizationIsNotInProgress
192
226
runEffectFn1 Profiling .end mark
193
227
228
+ mark2 <- runEffectFn1 Profiling .begin " drainEffects"
229
+ runEffectFn2 Queue .drain globalEffectQueue (mkEffectFn1 \handler -> handler)
230
+ runEffectFn1 Profiling .end mark2
231
+
194
232
recomputeNode :: EffectFn1 SomeNode Unit
195
233
recomputeNode = mkEffectFn1 \node -> do
196
234
height <- runEffectFn1 Node .get_height node
@@ -282,6 +320,60 @@ mapOptional = mkEffectFn2 \fn a -> do
282
320
, dependencies: pure deps
283
321
}
284
322
323
+ data AsyncComputation a = Sync a | Async (Aff a )
324
+
325
+ data AsyncState a
326
+ = InProgress (Maybe (Either Error a ))
327
+ | Finished (Either Error a )
328
+
329
+ derive instance Generic (AsyncState a ) _
330
+ instance Show a => Show (AsyncState a ) where
331
+ show = genericShow
332
+
333
+ mapAsync :: forall a b . EffectFn2 (a -> AsyncComputation b ) (Node a ) (Node (AsyncState b ))
334
+ mapAsync = mkEffectFn2 \fn a -> do
335
+ let deps = [ toSomeNode a ]
336
+ task <- ExclusiveTask .new
337
+ finishedRef <- runEffectFn1 Ref .new Nothing
338
+ runEffectFn1 Node .create
339
+ { compute: mkEffectFn1 \self -> do
340
+ -- Need to determine why we're updating - because the input changed, or because the computation finished?
341
+ finished <- runEffectFn1 Ref .read finishedRef
342
+ case finished of
343
+ Nothing -> do
344
+ value_a <- runEffectFn1 Node .valueExc a
345
+ case fn value_a of
346
+ Sync x ->
347
+ pure (Optional .some (Finished (Right x)))
348
+ Async aff -> do
349
+ nextMicrotask do
350
+ ExclusiveTask .run task do
351
+ newValue <- aff
352
+ liftEffect do
353
+ runEffectFn2 Ref .write finishedRef (Just (Right newValue))
354
+ runEffectFn1 addToRecomputeQueue self
355
+ stabilize
356
+ previous <- runEffectFn1 Node .get_value self
357
+ pure (Optional .some (InProgress (getPreviousValue previous)))
358
+ Just x -> do
359
+ -- Hmm. Can the input also be changing at the same time we're reporting the result of async computation?
360
+ -- Currently not, because we always stabilize after changing a node.
361
+ -- But if we introduced some batching later on, it could happen,
362
+ -- in which case we'd need to check `isChangingInCurrentStabilization` of our dependency.
363
+
364
+ runEffectFn2 Ref .write finishedRef Nothing
365
+ pure (Optional .some (Finished x))
366
+ , dependencies: pure deps
367
+ }
368
+
369
+ where
370
+ getPreviousValue opt
371
+ | Optional .isSome opt =
372
+ case Optional .fromSome opt of
373
+ InProgress x -> x
374
+ Finished x -> Just x
375
+ | otherwise = Nothing
376
+
285
377
map2 :: forall a b c . EffectFn3 (Fn2 a b c ) (Node a ) (Node b ) (Node c )
286
378
map2 = mkEffectFn3 \fn a b -> do
287
379
let deps = [ toSomeNode a, toSomeNode b ]
0 commit comments