Skip to content

Commit 6470c51

Browse files
authored
Improve memory usage with existential state (#21)
* Improve memory usage with existential state * Fix test * Remove ElemSpec * Tweak test diff, names
1 parent ab96274 commit 6470c51

File tree

8 files changed

+357
-233
lines changed

8 files changed

+357
-233
lines changed

src/Halogen/VDom.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ module Halogen.VDom
55
) where
66

77
import Halogen.VDom.DOM (VDomSpec(..), buildVDom) as DOM
8-
import Halogen.VDom.Machine (Machine, Step(..), extract, step, halt) as Machine
9-
import Halogen.VDom.Types (VDom(..), Graft, runGraft, ElemSpec(..), ElemName(..), Namespace(..)) as Types
8+
import Halogen.VDom.Machine (Machine, Step, Step'(..), mkStep, unStep, extract, step, halt) as Machine
9+
import Halogen.VDom.Types (VDom(..), Graft, runGraft, ElemName(..), Namespace(..)) as Types

src/Halogen/VDom/DOM.purs

Lines changed: 252 additions & 159 deletions
Large diffs are not rendered by default.

src/Halogen/VDom/DOM/Prop.purs

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Effect.Uncurried as EFn
2121
import Foreign (typeOf)
2222
import Foreign.Object as Object
2323
import Halogen.VDom as V
24+
import Halogen.VDom.Machine (Step'(..), mkStep)
2425
import Halogen.VDom.Types (Namespace(..))
2526
import Halogen.VDom.Util as Util
2627
import Unsafe.Coerce (unsafeCoerce)
@@ -71,35 +72,38 @@ buildProp
7172
. (a Effect Unit)
7273
DOM.Element
7374
V.Machine (Array (Prop a)) Unit
74-
buildProp emit el = render
75+
buildProp emit el = renderProp
7576
where
76-
render = EFn.mkEffectFn1 \ps1 → do
77+
renderProp = EFn.mkEffectFn1 \ps1 → do
7778
events ← Util.newMutMap
7879
ps1' ← EFn.runEffectFn3 Util.strMapWithIxE ps1 propToStrKey (applyProp events)
79-
pure
80-
(V.Step unit
81-
(Fn.runFn2 patch (Util.unsafeFreeze events) ps1')
82-
(done ps1'))
83-
84-
patch = Fn.mkFn2 \prevEvents ps1 →
85-
EFn.mkEffectFn1 \ps2 → do
86-
events ← Util.newMutMap
87-
let
88-
onThese = Fn.runFn2 diffProp prevEvents events
89-
onThis = removeProp prevEvents
90-
onThat = applyProp events
91-
ps2' ← EFn.runEffectFn6 Util.diffWithKeyAndIxE ps1 ps2 propToStrKey onThese onThis onThat
92-
pure
93-
(V.Step unit
94-
(Fn.runFn2 patch (Util.unsafeFreeze events) ps2')
95-
(done ps2'))
96-
97-
done ps =
98-
case Object.lookup "ref" ps of
80+
let
81+
state =
82+
{ events: Util.unsafeFreeze events
83+
, props: ps1'
84+
}
85+
pure $ mkStep $ Step unit state patchProp haltProp
86+
87+
patchProp = EFn.mkEffectFn2 \state ps2 → do
88+
events ← Util.newMutMap
89+
let
90+
{ events: prevEvents, props: ps1 } = state
91+
onThese = Fn.runFn2 diffProp prevEvents events
92+
onThis = removeProp prevEvents
93+
onThat = applyProp events
94+
props ← EFn.runEffectFn6 Util.diffWithKeyAndIxE ps1 ps2 propToStrKey onThese onThis onThat
95+
let
96+
nextState =
97+
{ events: Util.unsafeFreeze events
98+
, props
99+
}
100+
pure $ mkStep $ Step unit nextState patchProp haltProp
101+
102+
haltProp = EFn.mkEffectFn1 \state → do
103+
case Object.lookup "ref" state.props of
99104
Just (Ref f) →
100105
EFn.runEffectFn1 mbEmit (f (Removed el))
101-
_ →
102-
Util.effectUnit
106+
_ → pure unit
103107

104108
mbEmit = EFn.mkEffectFn1 case _ of
105109
Just a → emit a

src/Halogen/VDom/Machine.purs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
module Halogen.VDom.Machine
22
( Machine
3-
, Step(..)
3+
, Step'(..)
4+
, Step
5+
, mkStep
6+
, unStep
47
, extract
58
, step
69
, halt
710
) where
811

912
import Prelude
1013

11-
import Effect (Effect)
12-
import Effect.Uncurried (EffectFn1)
14+
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2, runEffectFn1, runEffectFn2)
15+
import Unsafe.Coerce (unsafeCoerce)
1316

1417
type Machine a b = EffectFn1 a (Step a b)
1518

16-
data Step a b = Step b (Machine a b) (Effect Unit)
19+
data Step' a b s = Step b s (EffectFn2 s a (Step a b)) (EffectFn1 s Unit)
20+
21+
foreign import data StepType Type Type
22+
23+
mkStep a b s. Step' a b s Step a b
24+
mkStep = unsafeCoerce
25+
26+
unStep :: a b r. ( s. Step' a b s r) Step a b r
27+
unStep = unsafeCoerce
1728

1829
-- | Returns the output value of a `Step`.
1930
extract a b. Step a b b
20-
extract (Step x _ _) = x
31+
extract = unStep \(Step x _ _ _) → x
2132

2233
-- | Runs the next step.
23-
step a b. Step a b EffectFn1 a (Step a b)
24-
step (Step _ m _) = m
34+
step a b. EffectFn2 (Step a b) a (Step a b)
35+
step = coerce $ mkEffectFn2 \(Step _ s k _) a → runEffectFn2 k s a
36+
where
37+
coerce s. EffectFn2 (Step' a b s) a (Step a b) EffectFn2 (Step a b) a (Step a b)
38+
coerce = unsafeCoerce
2539

2640
-- | Runs the finalizer associated with a `Step`
27-
halt a b. Step a b Effect Unit
28-
halt (Step _ _ h) = h
41+
halt a b. EffectFn1 (Step a b) Unit
42+
halt = coerce $ mkEffectFn1 \(Step _ s _ k) → runEffectFn1 k s
43+
where
44+
coerce s. EffectFn1 (Step' a b s) Unit EffectFn1 (Step a b) Unit
45+
coerce = unsafeCoerce

src/Halogen/VDom/Types.purs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ module Halogen.VDom.Types
55
, graft
66
, unGraft
77
, runGraft
8-
, ElemSpec(..)
98
, ElemName(..)
109
, Namespace(..)
1110
) where
@@ -25,8 +24,8 @@ import Unsafe.Coerce (unsafeCoerce)
2524
-- | fusion using a Coyoneda-like encoding.
2625
data VDom a w
2726
= Text String
28-
| Elem (ElemSpec a) (Array (VDom a w))
29-
| Keyed (ElemSpec a) (Array (Tuple String (VDom a w)))
27+
| Elem (Maybe Namespace) ElemName a (Array (VDom a w))
28+
| Keyed (Maybe Namespace) ElemName a (Array (Tuple String (VDom a w)))
3029
| Widget w
3130
| Grafted (Graft a w)
3231

@@ -72,19 +71,13 @@ runGraft =
7271
unGraft \(Graft fa fw v) →
7372
let
7473
go (Text s) = Text s
75-
go (Elem spec ch) = Elem (map fa spec) (map go ch)
76-
go (Keyed spec ch) = Keyed (map fa spec) (map (map go) ch)
74+
go (Elem ns n a ch) = Elem ns n (fa a) (map go ch)
75+
go (Keyed ns n a ch) = Keyed ns n (fa a) (map (map go) ch)
7776
go (Widget w) = Widget (fw w)
7877
go (Grafted g) = Grafted (bimap fa fw g)
7978
in
8079
go v
8180

82-
data ElemSpec a = ElemSpec (Maybe Namespace) ElemName a
83-
84-
derive instance eqElemSpecEq a Eq (ElemSpec a)
85-
derive instance ordElemSpecOrd a Ord (ElemSpec a)
86-
derive instance functorElemSpecFunctor ElemSpec
87-
8881
newtype ElemName = ElemName String
8982

9083
derive instance newtypeElemNameNewtype ElemName _

src/Halogen/VDom/Util.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ exports.unsafeDeleteAny = function (key, obj) {
1717
};
1818

1919
exports.forE = function (a, f) {
20-
var b = [];
21-
for (var i = 0; i < a.length; i++) {
22-
b.push(f(i, a[i]));
23-
}
24-
return b;
20+
var b = [];
21+
for (var i = 0; i < a.length; i++) {
22+
b.push(f(i, a[i]));
23+
}
24+
return b;
25+
};
26+
27+
exports.forEachE = function (a, f) {
28+
for (var i = 0; i < a.length; i++) {
29+
f(a[i]);
30+
}
2531
};
2632

2733
exports.forInE = function (o, f) {

src/Halogen/VDom/Util.purs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module Halogen.VDom.Util
2-
( effectPure
3-
, effectUnit
4-
, newMutMap
2+
( newMutMap
53
, pokeMutMap
64
, deleteMutMap
75
, unsafeFreeze
@@ -11,6 +9,7 @@ module Halogen.VDom.Util
119
, unsafeSetAny
1210
, unsafeDeleteAny
1311
, forE
12+
, forEachE
1413
, forInE
1514
, replicateE
1615
, diffWithIxE
@@ -48,12 +47,6 @@ import Web.DOM.Element (Element) as DOM
4847
import Web.DOM.Node (Node) as DOM
4948
import Web.Event.EventTarget (EventListener) as DOM
5049

51-
effectPure a. a Effect a
52-
effectPure = pure
53-
54-
effectUnit Effect Unit
55-
effectUnit = pure unit
56-
5750
newMutMap r a. Effect (STObject r a)
5851
newMutMap = unsafeCoerce STObject.new
5952

@@ -87,6 +80,13 @@ foreign import forE
8780
(EFn.EffectFn2 Int a b)
8881
(Array b)
8982

83+
foreign import forEachE
84+
a
85+
. EFn.EffectFn2
86+
(Array a)
87+
(EFn.EffectFn1 a Unit)
88+
Unit
89+
9090
foreign import forInE
9191
a
9292
. EFn.EffectFn2

test/Main.purs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ initialState ∷ State
5656
initialState = []
5757

5858
elem a w. String a Array (V.VDom a w) V.VDom a w
59-
elem n a = V.Elem (V.ElemSpec Nothing (V.ElemName n) a)
59+
elem n a = V.Elem Nothing (V.ElemName n) a
6060

6161
keyed a w. String a Array (Tuple String (V.VDom a w)) V.VDom a w
62-
keyed n a = V.Keyed (V.ElemSpec Nothing (V.ElemName n) a)
62+
keyed n a = V.Keyed Nothing (V.ElemName n) a
6363

6464
text a w. String V.VDom a w
6565
text = V.Text
@@ -108,24 +108,35 @@ renderData st =
108108
]
109109
]
110110

111+
type WidgetState a w =
112+
{ t :: Exists Thunk
113+
, step :: V.Step a w
114+
}
115+
111116
buildWidget
112117
V.VDomSpec (Array (Prop Void)) (Exists Thunk)
113118
V.Machine (Exists Thunk) DOM.Node
114119
buildWidget spec = render
115120
where
116121
render = EFn.mkEffectFn1 \t → case unsafeCoerce t of
117122
Thunk a render' → do
118-
V.Step node m h ← EFn.runEffectFn1 (V.buildVDom spec) (render' a)
119-
pure (V.Step node (Fn.runFn4 patch (unsafeCoerce a) node m h) h)
120-
121-
patch = Fn.mkFn4 \a node step halt →
122-
EFn.mkEffectFn1 \t → case unsafeCoerce t of
123-
Thunk b render' →
124-
if Fn.runFn2 refEq a b
125-
then pure (V.Step node (Fn.runFn4 patch a node step halt) halt)
123+
step ← EFn.runEffectFn1 (V.buildVDom spec) (render' a)
124+
let state = { t, step }
125+
pure (V.mkStep (V.Step (V.extract step) state patch done))
126+
127+
patch = EFn.mkEffectFn2 \state t →
128+
case unsafeCoerce state.t, unsafeCoerce t of
129+
Thunk a render1, Thunk b render2 →
130+
if Fn.runFn2 refEq a b && Fn.runFn2 refEq render1 render2
131+
then
132+
pure (V.mkStep (V.Step (V.extract state.step) state patch done))
126133
else do
127-
V.Step node' m h ← EFn.runEffectFn1 step (render' b)
128-
pure (V.Step node' (Fn.runFn4 patch (unsafeCoerce b) node' m h) h)
134+
step ← EFn.runEffectFn2 V.step state.step (render2 b)
135+
let nextState = { t, step }
136+
pure (V.mkStep (V.Step (V.extract step) nextState patch done))
137+
138+
done = EFn.mkEffectFn1 \state → do
139+
EFn.runEffectFn1 V.halt state.step
129140

130141
mkSpec
131142
DOM.Document
@@ -162,7 +173,7 @@ mkRenderQueue spec parent render initialValue = do
162173
when (isNothing v) $ requestAnimationFrame do
163174
machine ← Ref.read ref
164175
Ref.read val >>= traverse_ \v' → do
165-
res ← EFn.runEffectFn1 (V.step machine) (render v')
176+
res ← EFn.runEffectFn2 V.step machine (render v')
166177
Ref.write res ref
167178
Ref.write Nothing val
168179

@@ -179,7 +190,7 @@ mkRenderQueue' spec parent render initialValue = do
179190
ref ← Ref.new initMachine
180191
pure \v → do
181192
machine ← Ref.read ref
182-
res ← EFn.runEffectFn1 (V.step machine) (render v)
193+
res ← EFn.runEffectFn2 V.step machine (render v)
183194
Ref.write res ref
184195

185196
main Effect Unit

0 commit comments

Comments
 (0)