ron-rdt-0.11: lib/RON/Data/VersionVector.hs
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- | Version Vector
module RON.Data.VersionVector (
VersionVector,
VV (..),
lookup,
mkVV,
serializeVV,
(·≻),
(·≼),
) where
import RON.Prelude hiding (lookup)
import Data.Hashable (hashWithSalt)
import Data.Map.Strict qualified as Map
import RON.Data.Internal (
Reducible,
Rep,
Replicated (encoding),
ReplicatedAsObject,
getObjectState,
newObject,
objectEncoding,
readObject,
reducibleOpType,
stateFromChunk,
stateToChunk,
)
import RON.Event (
Event (Event),
Replica,
Time,
encodeEvent,
getEventUuid,
unsafeDecodeEvent,
)
import RON.Event qualified
import RON.Semilattice (Semilattice, (≼))
import RON.Text.Serialize (serializePayload)
import RON.Types (
Atom (AUuid),
ObjectRef (ObjectRef),
Op (Op, opId),
UUID (UUID),
WireStateChunk (WireStateChunk, stateBody, stateType),
)
import RON.UUID qualified as UUID
opTime :: Op -> Word64
opTime Op{opId = UUID time _} = time
-- | Assume opId is event
opReplica :: Op -> Replica
opReplica Op{opId} = (unsafeDecodeEvent opId).replica
latter :: Op -> Op -> Op
latter = maxOn opTime
-- | Version Vector type. May be used both in typed and untyped contexts.
newtype VersionVector = VersionVector (Map Replica Op)
deriving (Eq, Show)
instance Hashable VersionVector where
hashWithSalt s (VersionVector vv) = hashWithSalt s $ Map.assocs vv
instance Semigroup VersionVector where
(<>) = coerce $ Map.unionWith latter
instance Monoid VersionVector where
mempty = VersionVector mempty
{- | Laws:
1. Idempotent because 'Map.unionWith' is idempotent.
2. Commutative because 'latter' is commutative.
-}
instance Semilattice VersionVector where
VersionVector a ≼ VersionVector b =
case leftJoin (Map.assocs a) (Map.assocs b) of
Nothing -> False
Just joined -> and [opTime opA <= opTime opB | (opA, opB) <- joined]
where
-- leftJoin :: Ord k => [(k, v1)] -> [(k, v2)] -> Maybe [(v1, v2)]
leftJoin [] _ = Just []
leftJoin (_ : _) [] = Nothing
leftJoin ((xk, xv) : xs') ((yk, yv) : ys') =
case compare xk yk of
LT -> Nothing -- x must present but doesn't
EQ -> ((xv, yv) :) <$> leftJoin xs' ys' -- intersection
GT -> leftJoin xs' ys' -- skip extra y
instance Reducible VersionVector where
reducibleOpType = vvType
stateFromChunk ops =
VersionVector $ Map.fromListWith latter [(opReplica op, op) | op <- ops]
stateToChunk (VersionVector vv) = Map.elems vv
wireStateChunk :: [Op] -> WireStateChunk
wireStateChunk stateBody = WireStateChunk{stateType = vvType, stateBody}
-- | Name-UUID to use as Version Vector type marker.
vvType :: UUID
vvType = $(UUID.liftName "vv")
instance Replicated VersionVector where
encoding = objectEncoding
instance ReplicatedAsObject VersionVector where
type Rep VersionVector = VersionVector
newObject (VersionVector vv) = do
oid <- getEventUuid
let ops = Map.elems vv
modify' $ Map.insert oid $ wireStateChunk ops
pure $ ObjectRef oid
readObject = getObjectState
lookup :: Replica -> VersionVector -> Maybe Op
lookup replica (VersionVector vv) = Map.lookup replica vv
-- | Simplified version vector
newtype VV = VV (Map Replica Time)
deriving (Show)
instance Semigroup VV where
VV a <> VV b = VV $ Map.unionWith max a b
instance Monoid VV where
mempty = VV Map.empty
-- | Assuming all UUIDs are events.
mkVV :: [UUID] -> VV
mkVV uuids =
VV $
Map.fromList
[ (replica, time)
| uuid <- uuids
, let Event{replica, time} = unsafeDecodeEvent uuid
]
unVV :: VV -> [UUID]
unVV (VV vv) =
[encodeEvent Event{replica, time} | (replica, time) <- Map.assocs vv]
(·≻) :: UUID -> VV -> Bool
uuid ·≻ VV vv =
maybe True (time >) $ Map.lookup replica vv
where
Event{replica, time} = unsafeDecodeEvent uuid
(·≼) :: UUID -> VV -> Bool
uuid ·≼ VV vv =
maybe False (time <=) $ Map.lookup replica vv
where
Event{replica, time} = unsafeDecodeEvent uuid
serializeVV :: VV -> ByteStringL
serializeVV = serializePayload . map AUuid . unVV