moonlight-delta-0.1.0.0: src-patch/Moonlight/Delta/Patch/Internal/Replay.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
-- Loop-local rebinding (state/cursor/coverage) is the engine idiom here; shadowing is deliberate.
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module Moonlight.Delta.Patch.Internal.Replay
( replay,
)
where
import Data.Bits (testBit)
import Data.Foldable qualified as Foldable
-- Data.Map.Internal: balance-aware spine traversal; semi-stable API accepted deliberately, pinned by containers < 0.9.
import Data.Map.Internal qualified as MapInternal
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Primitive.SmallArray
( indexSmallArray,
sizeofSmallArray,
)
import Moonlight.Delta.Patch.Internal.Apply
( apply,
applyTrusted,
applyTrustedEdit,
shouldUseSparse,
)
import Moonlight.Delta.Patch.Internal.Cell
( cellAfter,
cellBefore,
)
import Moonlight.Delta.Patch.Internal.Compose.Core
( ComposeResult (..),
Coverage (..),
NewKeyPolicy (..),
composeWith,
)
import Moonlight.Delta.Patch.Internal.Compose.SameSupport
( SameSupport,
extendSameSupport,
reflexiveSupport,
sameSupportLatest,
spliceSameSupport,
validateBoundarySameSupport,
)
import Moonlight.Delta.Patch.Internal.Cursor
( AscCursor (..),
Cursor (CursorEnd),
ascAdvance,
ascCursor,
advanceRow,
afterMaybe,
beforeMaybe,
currentKey,
cursor,
)
import Moonlight.Delta.Patch.Internal.Types
( ApplyError (..),
EndpointColumn (..),
Cell (..),
Patch (..),
PatchKey,
PatchValue,
Page (..),
ReplayError (..),
pageCapacity,
entryCount,
valueColumnAt,
)
import Numeric.Natural (Natural)
import Prelude
data StateProbe key value
= ProbeFresh !(Map key value) !(AscCursor key value)
| ProbeAfter !(Map key value) !key !(AscCursor key value)
| ProbeRandom !(Map key value)
newStateProbe :: Map key value -> StateProbe key value
newStateProbe state =
ProbeFresh state (ascCursor state)
{-# INLINE newStateProbe #-}
probeInitialState :: Ord key => key -> StateProbe key value -> (Maybe value, StateProbe key value)
probeInitialState key probe =
case probe of
ProbeFresh state cursor ->
let (!result, !nextCursor) = lookupAscending key cursor
in (result, ProbeAfter state key nextCursor)
ProbeAfter state previousKey cursor
| previousKey < key ->
let (!result, !nextCursor) = lookupAscending key cursor
in (result, ProbeAfter state key nextCursor)
| otherwise ->
(Map.lookup key state, ProbeRandom state)
ProbeRandom state ->
(Map.lookup key state, ProbeRandom state)
{-# INLINABLE probeInitialState #-}
lookupAscending :: Ord key => key -> AscCursor key value -> (Maybe value, AscCursor key value)
lookupAscending _ AscEnd =
(Nothing, AscEnd)
lookupAscending target cursor@(AscCursor key value _ _) =
case compare key target of
LT -> lookupAscending target (ascAdvance cursor)
EQ -> (Just value, cursor)
GT -> (Nothing, cursor)
{-# INLINABLE lookupAscending #-}
data ReplayAccumulator key value
= ReplayStable !(SameSupport key value)
| ReplayGeneral !(Patch key value)
| ReplayArena !(Map key (Maybe value, Maybe value))
data SingletonReplayArena key value
= SingletonArenaAscending !key ![(key, (Maybe value, Maybe value))]
| SingletonArenaMap !(Map key (Maybe value, Maybe value))
data ArenaBulkChanges key value
= ArenaNoChanges
| ArenaInsertions !(Map key value)
| ArenaDeletions !(Map key ())
| ArenaMixedChanges !(Map key value) !(Map key ())
data AscendingArenaRows key value = AscendingArenaRows ![(key, value)] ![(key, ())]
data ReplayMergeCursor key value = ReplayMergeCursor !(AscCursor key value) !(AscCursor key (Maybe value, Maybe value))
data ReplayOutput key value = ReplayOutput !key !value !(ReplayMergeCursor key value)
data BuildReplayResult key value = BuildReplayResult !(Map key value) !(ReplayMergeCursor key value)
replay ::
forall patches key value.
(Foldable patches, PatchKey key, PatchValue value) =>
patches (Patch key value) ->
Map key value ->
Either (ReplayError key value) (Map key value)
replay patches initialState =
case Foldable.toList patches of
[] ->
Right initialState
first : remaining -> do
probe <- validateFirstTouches 0 (newStateProbe initialState) first
continue 1 probe (ReplayStable (reflexiveSupport first)) remaining
where
continue !_patchIndex !_probe accumulator [] =
Right
( case accumulator of
ReplayStable sameSupport ->
applyTrusted (spliceSameSupport sameSupport) initialState
ReplayGeneral patch ->
applyTrusted patch initialState
ReplayArena arena ->
applyArenaDirect arena initialState
)
continue !patchIndex !probe accumulator (nextPatch : remaining) =
case accumulator of
ReplayStable stableRun ->
let !latest = sameSupportLatest stableRun
in case validateBoundarySameSupport (replayBoundaryError patchIndex) latest nextPatch of
Left failure ->
Left failure
Right (Just latestToNext) ->
continue
(patchIndex + 1)
probe
(ReplayStable (extendSameSupport stableRun latestToNext))
remaining
Right Nothing ->
composeDivergent patchIndex probe (spliceSameSupport stableRun) nextPatch remaining
ReplayGeneral accumulated ->
case validateBoundarySameSupport (replayBoundaryError patchIndex) accumulated nextPatch of
Left failure ->
Left failure
Right (Just sameSupport) ->
continue (patchIndex + 1) probe (ReplayStable sameSupport) remaining
Right Nothing ->
composeDivergent patchIndex probe accumulated nextPatch remaining
ReplayArena arena -> do
nextArena <- applyPatchToArena patchIndex initialState nextPatch arena
continue (patchIndex + 1) probe (ReplayArena nextArena) remaining
composeDivergent !patchIndex !probe !accumulated !nextPatch remaining
| useSequentialReplay accumulated nextPatch =
replaySingletonArenaFrom
patchIndex
initialState
probe
(singletonArenaFromPatch accumulated)
(nextPatch : remaining)
| otherwise = do
ComposeResult
{ patch = combined,
state = nextProbe,
coverage = coverage
} <-
composeWith
(CheckNewKeys (validateInitialBefore patchIndex))
probe
(replayBoundaryError patchIndex)
accumulated
nextPatch
let !nextAccumulator =
if useReplayArena coverage accumulated nextPatch
then ReplayArena (arenaFromPatch combined)
else
if coverageOlderOnly coverage
then ReplayGeneral combined
else ReplayStable (reflexiveSupport combined)
continue (patchIndex + 1) nextProbe nextAccumulator remaining
{-# INLINABLE replay #-}
{-# SPECIALIZE replay ::
(PatchKey key, PatchValue value) =>
[Patch key value] ->
Map key value ->
Either (ReplayError key value) (Map key value)
#-}
replaySequentiallyFrom ::
(Ord key, Eq value) =>
Natural ->
Map key value ->
[Patch key value] ->
Either (ReplayError key value) (Map key value)
replaySequentiallyFrom !_patchIndex !state [] =
Right state
replaySequentiallyFrom !patchIndex !state (patch : remaining) =
case apply patch state of
Left applyError ->
Left
ReplayApplyError
{ replayIndex = patchIndex,
replayApply = applyError
}
Right !nextState ->
replaySequentiallyFrom (patchIndex + 1) nextState remaining
{-# INLINABLE replaySequentiallyFrom #-}
replaySingletonArenaFrom ::
(Ord key, Eq value) =>
Natural ->
Map key value ->
StateProbe key value ->
SingletonReplayArena key value ->
[Patch key value] ->
Either (ReplayError key value) (Map key value)
replaySingletonArenaFrom !_patchIndex !initialState !_probe !arena [] =
Right (applySingletonReplayArena arena initialState)
replaySingletonArenaFrom !patchIndex !initialState !probe !arena patches@(patch : remaining) =
case singletonPatchRow patch of
Nothing ->
replaySequentiallyFrom patchIndex (applySingletonReplayArena arena initialState) patches
Just (key, expectedBefore, currentAfter) ->
case applySingletonToReplayArena patchIndex key expectedBefore currentAfter probe arena of
Left failure ->
Left failure
Right (!nextProbe, !nextArena) ->
replaySingletonArenaFrom (patchIndex + 1) initialState nextProbe nextArena remaining
{-# INLINABLE replaySingletonArenaFrom #-}
singletonArenaFromPatch :: Ord key => Patch key value -> SingletonReplayArena key value
singletonArenaFromPatch patch =
case singletonPatchRow patch of
Just (key, before, after) ->
SingletonArenaAscending key [(key, (before, after))]
Nothing ->
SingletonArenaMap (arenaFromPatch patch)
{-# INLINABLE singletonArenaFromPatch #-}
applySingletonReplayArena :: Ord key => SingletonReplayArena key value -> Map key value -> Map key value
applySingletonReplayArena arena initialState =
case arena of
SingletonArenaAscending _ rows ->
applyArenaBulkChanges (ascendingSingletonArenaBulkChanges rows) initialState
SingletonArenaMap entries ->
applyArenaDirect entries initialState
{-# INLINABLE applySingletonReplayArena #-}
ascendingSingletonArenaBulkChanges :: [(key, (Maybe value, Maybe value))] -> ArenaBulkChanges key value
ascendingSingletonArenaBulkChanges rows =
let !(AscendingArenaRows insertionRows deletionRows) =
Foldable.foldl' collectRow (AscendingArenaRows [] []) rows
!insertions = Map.fromDistinctAscList insertionRows
!deletions = Map.fromDistinctAscList deletionRows
in case (Map.null insertions, Map.null deletions) of
(True, True) ->
ArenaNoChanges
(False, True) ->
ArenaInsertions insertions
(True, False) ->
ArenaDeletions deletions
(False, False) ->
ArenaMixedChanges insertions deletions
where
collectRow ::
AscendingArenaRows key value ->
(key, (Maybe value, Maybe value)) ->
AscendingArenaRows key value
collectRow (AscendingArenaRows insertions deletions) (key, (_initial, current)) =
case current of
Nothing ->
AscendingArenaRows insertions ((key, ()) : deletions)
Just value ->
AscendingArenaRows ((key, value) : insertions) deletions
{-# INLINABLE ascendingSingletonArenaBulkChanges #-}
applyArenaBulkChanges :: Ord key => ArenaBulkChanges key value -> Map key value -> Map key value
applyArenaBulkChanges changes initialState =
case changes of
ArenaNoChanges ->
initialState
ArenaInsertions insertions ->
Map.union insertions initialState
ArenaDeletions deletions ->
Map.difference initialState deletions
ArenaMixedChanges insertions deletions ->
Map.union insertions (Map.difference initialState deletions)
{-# INLINABLE applyArenaBulkChanges #-}
applySingletonToReplayArena ::
(Ord key, Eq value) =>
Natural ->
key ->
Maybe value ->
Maybe value ->
StateProbe key value ->
SingletonReplayArena key value ->
Either (ReplayError key value) (StateProbe key value, SingletonReplayArena key value)
applySingletonToReplayArena patchIndex key expectedBefore currentAfter probe arena =
case arena of
SingletonArenaAscending latestKey rows
| latestKey < key ->
let (!initialBefore, !nextProbe) = probeInitialState key probe
in if expectedBefore == initialBefore
then
Right
( nextProbe,
SingletonArenaAscending key ((key, (initialBefore, currentAfter)) : rows)
)
else
Left
ReplayApplyError
{ replayIndex = patchIndex,
replayApply =
ApplyBeforeMismatch
{ mismatchKey = key,
expectedBefore = expectedBefore,
actualBefore = initialBefore
}
}
| otherwise ->
promote (Map.fromDistinctAscList (reverse rows))
SingletonArenaMap entries ->
promote entries
where
promote entries = do
(!nextProbe, !nextEntries) <- applySingletonToArena patchIndex key expectedBefore currentAfter probe entries
pure (nextProbe, SingletonArenaMap nextEntries)
{-# INLINABLE applySingletonToReplayArena #-}
singletonPatchRow :: Patch key value -> Maybe (key, Maybe value, Maybe value)
singletonPatchRow patch
| entryCount patch /= 1 =
Nothing
| otherwise =
case patch of
SmallPatch cells ->
case indexSmallArray cells 0 of
Cell key cell ->
Just (key, cellBefore cell, cellAfter cell)
PagedPatch _count pages ->
case pages of
MapInternal.Bin _ key page MapInternal.Tip MapInternal.Tip
| pageCount page == 1 ->
Just
( key,
singletonEndpointMaybe (pageBeforeColumn page),
singletonEndpointMaybe (pageAfterColumn page)
)
_ ->
Nothing
{-# INLINE singletonPatchRow #-}
singletonEndpointMaybe :: EndpointColumn value -> Maybe value
singletonEndpointMaybe column =
case column of
AllPresent values ->
Just (valueColumnAt values 0)
Presence mask values ->
if testBit mask 0
then Just (valueColumnAt values 0)
else Nothing
{-# INLINE singletonEndpointMaybe #-}
applySingletonToArena ::
(Ord key, Eq value) =>
Natural ->
key ->
Maybe value ->
Maybe value ->
StateProbe key value ->
Map key (Maybe value, Maybe value) ->
Either (ReplayError key value) (StateProbe key value, Map key (Maybe value, Maybe value))
applySingletonToArena patchIndex key expectedBefore currentAfter probe =
descend
where
descend entries =
case entries of
MapInternal.Tip ->
let (!initialBefore, !nextProbe) = probeInitialState key probe
in validate initialBefore nextProbe (Map.singleton key (initialBefore, currentAfter))
MapInternal.Bin nodeSize existingKey existingEntry left right ->
case compare key existingKey of
LT -> do
(!nextProbe, !updatedLeft) <- descend left
pure (nextProbe, MapInternal.link existingKey existingEntry updatedLeft right)
GT -> do
(!nextProbe, !updatedRight) <- descend right
pure (nextProbe, MapInternal.link existingKey existingEntry left updatedRight)
EQ ->
let (!initialBefore, !actualBefore) = existingEntry
in validate
actualBefore
probe
(MapInternal.Bin nodeSize key (initialBefore, currentAfter) left right)
validate actualBefore nextProbe updatedArena
| expectedBefore == actualBefore =
Right (nextProbe, updatedArena)
| otherwise =
Left
ReplayApplyError
{ replayIndex = patchIndex,
replayApply =
ApplyBeforeMismatch
{ mismatchKey = key,
expectedBefore = expectedBefore,
actualBefore = actualBefore
}
}
{-# INLINABLE applySingletonToArena #-}
useSequentialReplay :: Patch key patch -> Patch key patch -> Bool
useSequentialReplay accumulated nextPatch =
entryCount accumulated == 1
&& entryCount nextPatch == 1
{-# INLINE useSequentialReplay #-}
useReplayArena :: Coverage -> Patch key value -> Patch key value -> Bool
useReplayArena coverage accumulated nextPatch =
coverageOlderOnly coverage
&& not (coverageNewerOnly coverage)
&& entryCount nextPatch <= entryCount accumulated `div` sparseSubsetReplayFactor
{-# INLINE useReplayArena #-}
sparseSubsetReplayFactor :: Int
sparseSubsetReplayFactor = 4
{-# INLINE sparseSubsetReplayFactor #-}
arenaFromPatch :: Ord key => Patch key value -> Map key (Maybe value, Maybe value)
arenaFromPatch =
foldPatchRows
( \arena key initial current ->
insertArenaEntry key (initial, current) arena
)
Map.empty
{-# INLINABLE arenaFromPatch #-}
applyPatchToArena ::
forall key value.
(Ord key, Eq value) =>
Natural ->
Map key value ->
Patch key value ->
Map key (Maybe value, Maybe value) ->
Either (ReplayError key value) (Map key (Maybe value, Maybe value))
applyPatchToArena patchIndex initialState patch initialArena =
case patch of
SmallPatch cells ->
goSmall cells 0 initialArena
PagedPatch _count pages ->
go initialArena (cursor pages)
where
go !arena CursorEnd =
Right arena
go !arena cursor =
case currentKey cursor of
Nothing ->
Right arena
Just key ->
case stepArena key (beforeMaybe cursor) (afterMaybe cursor) arena of
Left failure ->
Left failure
Right nextArena ->
go nextArena (advanceRow cursor)
goSmall cells !index !arena
| index == sizeofSmallArray cells =
Right arena
| otherwise =
case indexSmallArray cells index of
Cell key cell ->
case stepArena key (cellBefore cell) (cellAfter cell) arena of
Left failure ->
Left failure
Right nextArena ->
goSmall cells (index + 1) nextArena
stepArena !key !expected !currentAfter !arena =
let (!initialBefore, !actualBefore) =
case Map.lookup key arena of
Just (initial, current) ->
(initial, current)
Nothing ->
let !initial = Map.lookup key initialState
in (initial, initial)
in if expected == actualBefore
then Right (insertArenaEntry key (initialBefore, currentAfter) arena)
else
Left
ReplayApplyError
{ replayIndex = patchIndex,
replayApply =
ApplyBeforeMismatch
{ mismatchKey = key,
expectedBefore = expected,
actualBefore = actualBefore
}
}
{-# INLINABLE applyPatchToArena #-}
insertArenaEntry :: Ord key => key -> (Maybe value, Maybe value) -> Map key (Maybe value, Maybe value) -> Map key (Maybe value, Maybe value)
insertArenaEntry patchKey entry =
descend
where
descend entries =
case entries of
MapInternal.Tip ->
Map.singleton patchKey entry
MapInternal.Bin nodeSize existingKey existingEntry left right ->
case compare patchKey existingKey of
LT ->
MapInternal.link existingKey existingEntry (descend left) right
GT ->
MapInternal.link existingKey existingEntry left (descend right)
EQ ->
MapInternal.Bin nodeSize patchKey entry left right
{-# INLINABLE insertArenaEntry #-}
applyArenaDirect :: Ord key => Map key (Maybe value, Maybe value) -> Map key value -> Map key value
applyArenaDirect arena initialState
| useArenaPointwiseFinalization arena initialState =
applyArenaPointwise arena initialState
| useArenaBulkFinalization arena initialState =
applyArenaBulk arena initialState
| otherwise =
case replayOutputCountCandidate arena initialState of
Nothing ->
applyArenaBulk arena initialState
Just outputCount ->
case buildReplayMap outputCount initialCursor of
Just (BuildReplayResult result remaining)
| drainReplayCursor remaining ->
result
_ ->
applyArenaBulk arena initialState
where
!initialCursor =
ReplayMergeCursor (ascCursor initialState) (ascCursor arena)
{-# INLINABLE applyArenaDirect #-}
useArenaPointwiseFinalization :: Map key patch -> Map key value -> Bool
useArenaPointwiseFinalization arena initialState =
Map.size arena <= pageCapacity
&& shouldUseSparse (Map.size initialState) (Map.size arena)
{-# INLINE useArenaPointwiseFinalization #-}
useArenaBulkFinalization :: Map key patch -> Map key value -> Bool
useArenaBulkFinalization arena initialState =
shouldUseSparse (Map.size initialState) (Map.size arena)
{-# INLINE useArenaBulkFinalization #-}
replayOutputCountCandidate :: Map key (Maybe value, Maybe value) -> Map key value -> Maybe Int
replayOutputCountCandidate arena initialState =
let !outputCount = Map.size initialState + arenaNetSizeDelta arena
in if outputCount < 0 then Nothing else Just outputCount
{-# INLINE replayOutputCountCandidate #-}
arenaNetSizeDelta :: Map key (Maybe value, Maybe value) -> Int
arenaNetSizeDelta =
Map.foldl' addEntry 0
where
addEntry :: Int -> (Maybe value, Maybe value) -> Int
addEntry !total entry =
total + arenaEntryNetSizeDelta entry
{-# INLINE arenaNetSizeDelta #-}
arenaEntryNetSizeDelta :: (Maybe value, Maybe value) -> Int
arenaEntryNetSizeDelta endpoints =
case endpoints of
(Nothing, Just _) ->
1
(Just _, Nothing) ->
-1
_ ->
0
{-# INLINE arenaEntryNetSizeDelta #-}
drainReplayCursor :: Ord key => ReplayMergeCursor key value -> Bool
drainReplayCursor cursor =
case nextReplayOutput cursor of
Nothing ->
True
Just _ ->
False
{-# INLINABLE drainReplayCursor #-}
buildReplayMap :: Ord key => Int -> ReplayMergeCursor key value -> Maybe (BuildReplayResult key value)
buildReplayMap !outputCount cursor
| outputCount <= 0 =
Just (BuildReplayResult MapInternal.Tip cursor)
| otherwise = do
let !leftCount = outputCount `quot` 2
!rightCount = outputCount - leftCount - 1
BuildReplayResult left afterLeft <- buildReplayMap leftCount cursor
ReplayOutput key value afterMiddle <- nextReplayOutput afterLeft
BuildReplayResult right afterRight <- buildReplayMap rightCount afterMiddle
Just (BuildReplayResult (MapInternal.Bin outputCount key value left right) afterRight)
{-# INLINABLE buildReplayMap #-}
nextReplayOutput :: forall key value. Ord key => ReplayMergeCursor key value -> Maybe (ReplayOutput key value)
nextReplayOutput (ReplayMergeCursor stateCursor arenaCursor) =
case (stateCursor, arenaCursor) of
(AscEnd, AscEnd) ->
Nothing
(AscCursor stateKey stateValue _ _, AscEnd) ->
Just (ReplayOutput stateKey stateValue (ReplayMergeCursor (ascAdvance stateCursor) AscEnd))
(AscEnd, AscCursor arenaKey (_initial, current) _ _) ->
emitArenaCurrent arenaKey current (ReplayMergeCursor AscEnd (ascAdvance arenaCursor))
(AscCursor stateKey stateValue _ _, AscCursor arenaKey (_initial, current) _ _) ->
case compare stateKey arenaKey of
LT ->
Just (ReplayOutput stateKey stateValue (ReplayMergeCursor (ascAdvance stateCursor) arenaCursor))
GT ->
emitArenaCurrent arenaKey current (ReplayMergeCursor stateCursor (ascAdvance arenaCursor))
EQ ->
emitArenaCurrent arenaKey current (ReplayMergeCursor (ascAdvance stateCursor) (ascAdvance arenaCursor))
where
emitArenaCurrent :: key -> Maybe value -> ReplayMergeCursor key value -> Maybe (ReplayOutput key value)
emitArenaCurrent key current nextCursor =
case current of
Nothing ->
nextReplayOutput nextCursor
Just value ->
Just (ReplayOutput key value nextCursor)
{-# INLINABLE nextReplayOutput #-}
applyArenaPointwise :: forall key value. Ord key => Map key (Maybe value, Maybe value) -> Map key value -> Map key value
applyArenaPointwise arena initialState =
Map.foldlWithKey' applyArenaEntry initialState arena
where
applyArenaEntry :: Map key value -> key -> (Maybe value, Maybe value) -> Map key value
applyArenaEntry state key (_initial, current) =
applyTrustedEdit key current state
{-# INLINABLE applyArenaPointwise #-}
applyArenaBulk :: Ord key => Map key (Maybe value, Maybe value) -> Map key value -> Map key value
applyArenaBulk arena initialState =
let !insertions = Map.mapMaybe arenaCurrentPresent arena
!deletions = Map.mapMaybe arenaCurrentAbsent arena
in Map.union insertions (Map.difference initialState deletions)
where
arenaCurrentPresent :: (Maybe value, Maybe value) -> Maybe value
arenaCurrentPresent (_initial, current) =
current
arenaCurrentAbsent :: (Maybe value, Maybe value) -> Maybe ()
arenaCurrentAbsent (_initial, current) =
case current of
Nothing ->
Just ()
Just _ ->
Nothing
{-# INLINABLE applyArenaBulk #-}
foldPatchRows ::
(result -> key -> Maybe value -> Maybe value -> result) ->
result ->
Patch key value ->
result
foldPatchRows step initial patch =
case patch of
SmallPatch cells ->
goSmall initial cells 0
PagedPatch _count pages ->
go initial (cursor pages)
where
go !result CursorEnd =
result
go !result cursor =
case currentKey cursor of
Nothing ->
result
Just key ->
go
(step result key (beforeMaybe cursor) (afterMaybe cursor))
(advanceRow cursor)
goSmall !result cells !index
| index == sizeofSmallArray cells =
result
| otherwise =
case indexSmallArray cells index of
Cell key cell ->
goSmall
(step result key (cellBefore cell) (cellAfter cell))
cells
(index + 1)
{-# INLINABLE foldPatchRows #-}
validateFirstTouches ::
forall key value.
(Ord key, Eq value) =>
Natural ->
StateProbe key value ->
Patch key value ->
Either (ReplayError key value) (StateProbe key value)
validateFirstTouches patchIndex =
go
where
go !probe patch =
case patch of
SmallPatch cells ->
scanSmall probe cells 0
PagedPatch _count pages ->
scan probe (cursor pages)
scan !probe CursorEnd =
Right probe
scan !probe cursor =
case currentKey cursor of
Nothing ->
Right probe
Just key ->
case validateInitialBefore patchIndex probe key (beforeMaybe cursor) of
Left failure ->
Left failure
Right nextProbe ->
scan nextProbe (advanceRow cursor)
scanSmall !probe cells !index
| index == sizeofSmallArray cells =
Right probe
| otherwise =
case indexSmallArray cells index of
Cell key cell ->
case validateInitialBefore patchIndex probe key (cellBefore cell) of
Left failure ->
Left failure
Right nextProbe ->
scanSmall nextProbe cells (index + 1)
{-# INLINABLE validateFirstTouches #-}
validateInitialBefore ::
forall key value.
(Ord key, Eq value) =>
Natural ->
StateProbe key value ->
key ->
Maybe value ->
Either (ReplayError key value) (StateProbe key value)
validateInitialBefore patchIndex probe key expected =
let (!actual, !nextProbe) = probeInitialState key probe
in if expected == actual
then Right nextProbe
else
Left
ReplayApplyError
{ replayIndex = patchIndex,
replayApply =
ApplyBeforeMismatch
{ mismatchKey = key,
expectedBefore = expected,
actualBefore = actual
}
}
{-# INLINABLE validateInitialBefore #-}
replayBoundaryError :: Natural -> key -> Maybe value -> Maybe value -> ReplayError key value
replayBoundaryError patchIndex key olderAfter newerBefore =
ReplayApplyError
{ replayIndex = patchIndex,
replayApply =
ApplyBeforeMismatch
{ mismatchKey = key,
expectedBefore = newerBefore,
actualBefore = olderAfter
}
}
{-# INLINE replayBoundaryError #-}