ron-0.12: lib/RON/Event.hs
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
module RON.Event (
CalendarTime (..),
Event (..),
Time (..),
TimeVariety (.., Calendar, Logical, Epoch, Unknown),
OriginVariety
(.., TrieForked, CryptoForked, RecordForked, ApplicationSpecific),
ReplicaClock (..),
Replica (..),
advanceToUuid,
decodeCalendar,
decodeEvent,
decodeReplica,
encodeCalendar,
encodeEvent,
getEvent,
getEventUuid,
getEventUuids,
mkCalendarDate,
mkCalendarDateTime,
mkCalendarDateTimeNano,
mkCalendarEvent,
mkReplica,
mkTime,
timeValue,
timeVariety,
) where
import RON.Prelude
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import qualified Data.ByteString.Char8 as BSC
import Data.Time (fromGregorianValid, makeTimeOfDayValid)
import qualified Text.Show
import RON.Base64 (encode60short)
import RON.Util.Word (pattern B00, pattern B01, pattern B10,
pattern B11, Word12, Word2, Word24, Word6,
Word60, leastSignificant12, leastSignificant2,
leastSignificant24, leastSignificant6, ls12,
ls24, ls6, ls60, safeCast)
import RON.UUID (UUID (..), UuidFields (..))
import qualified RON.UUID as UUID
-- | Calendar format. See https://github.com/gritzko/ron/issues/19.
-- Year range is 2010—2350.
-- Precision is 100 ns.
data CalendarTime = CalendarTime
{ months :: Word12
, days :: Word6
, hours :: Word6
, minutes :: Word6
, seconds :: Word6
, nanosecHundreds :: Word24
}
deriving (Eq, Ord, Show)
newtype TimeVariety = TimeVariety Word2
pattern Calendar :: TimeVariety
pattern Calendar = TimeVariety B00
pattern Logical :: TimeVariety
pattern Logical = TimeVariety B01
-- | RFC 4122 epoch, hundreds of nanoseconds since 1582.
-- Year range is 1582—5235.
pattern Epoch :: TimeVariety
pattern Epoch = TimeVariety B10
pattern Unknown :: TimeVariety
pattern Unknown = TimeVariety B11
{-# COMPLETE Calendar, Logical, Epoch, Unknown #-}
instance Show TimeVariety where
show = \case
Calendar -> "Calendar"
Logical -> "Logical"
Epoch -> "Epoch"
Unknown -> "Unknown"
-- | Clock type is encoded in 2 higher bits of variety, value in uuidValue
newtype Time = Time Word64
deriving (Eq, Ord)
instance Show Time where
show t =
show (timeVariety t) ++ '/' : BSC.unpack (encode60short $ timeValue t)
timeVariety :: Time -> TimeVariety
timeVariety (Time w64) = TimeVariety $ leastSignificant2 $ w64 `shiftR` 62
timeValue :: Time -> Word60
timeValue (Time w64) = ls60 w64
-- | Replica id assignment style
newtype OriginVariety = OriginVariety Word2
deriving newtype (Hashable)
pattern TrieForked :: OriginVariety
pattern TrieForked = OriginVariety B00
pattern CryptoForked :: OriginVariety
pattern CryptoForked = OriginVariety B01
pattern RecordForked :: OriginVariety
pattern RecordForked = OriginVariety B10
pattern ApplicationSpecific :: OriginVariety
pattern ApplicationSpecific = OriginVariety B11
{-# COMPLETE TrieForked, CryptoForked, RecordForked, ApplicationSpecific #-}
instance Show OriginVariety where
show = \case
TrieForked -> "Trie"
CryptoForked -> "Crypto"
RecordForked -> "Record"
ApplicationSpecific -> "App"
-- | Replica identifier.
-- Implementation: naming (62-61) and origin (60-0 bits) fields from UUID
newtype Replica = Replica Word64
deriving newtype (Eq, Hashable, Ord)
instance Show Replica where
show (Replica w64) =
show (OriginVariety $ leastSignificant2 $ w64 `shiftR` 60)
++ '/' : BSC.unpack (encode60short $ ls60 w64)
-- | Generic Lamport time event.
-- Cannot be 'Ord' because we can't compare different types of clocks.
-- If you want comparable events, use specific 'EpochEvent'.
data Event = Event
{ time :: !Time
, replica :: !Replica
}
deriving (Eq, Generic, Show)
class Monad m => ReplicaClock m where
-- | Get current replica id
getPid :: m Replica
-- | Get sequential timestamps.
--
-- Laws:
--
-- 1. @
--t <- getEvents n
--(t !! i) == head t + i
-- @
--
-- 2. @
--t1 <- 'getEvent'
--t2 <- 'getEvent'
--t2 >= t1 + 1
-- @
--
-- 3. @getEvents 0 == getEvents 1@
getEvents
:: Word60 -- ^ number of needed timestamps
-> m [Event]
-- | Make local time not less than this
advance :: Word60 -> m ()
instance ReplicaClock m => ReplicaClock (ExceptT e m) where
getPid = lift getPid
getEvents = lift . getEvents
advance = lift . advance
instance ReplicaClock m => ReplicaClock (ReaderT r m) where
getPid = lift getPid
getEvents = lift . getEvents
advance = lift . advance
instance ReplicaClock m => ReplicaClock (StateT s m) where
getPid = lift getPid
getEvents = lift . getEvents
advance = lift . advance
instance (Monoid s, ReplicaClock m) => ReplicaClock (WriterT s m) where
getPid = lift getPid
getEvents = lift . getEvents
advance = lift . advance
-- | 'advance' variant for any UUID
advanceToUuid :: ReplicaClock clock => UUID -> clock ()
advanceToUuid uuid =
when (uuidVariant == B00 && uuidVersion == B10) $ advance uuidValue
where
UuidFields{uuidValue, uuidVariant, uuidVersion} = UUID.split uuid
-- | Get a single event
getEvent :: (HasCallStack, ReplicaClock m) => m Event
getEvent = getEvents (ls60 1) >>= \case
e:_ -> pure e
[] -> error "getEvents returned no events"
-- | Get a single event as UUID
getEventUuid :: ReplicaClock m => m UUID
getEventUuid = encodeEvent <$> getEvent
-- | Get event sequence as UUIDs
getEventUuids :: ReplicaClock m => Word60 -> m [UUID]
getEventUuids = fmap (map encodeEvent) . getEvents
encodeCalendar :: CalendarTime -> Word60
encodeCalendar CalendarTime{..} = ls60 $
(safeCast months `shiftL` 48) .|.
(safeCast days `shiftL` 42) .|.
(safeCast hours `shiftL` 36) .|.
(safeCast minutes `shiftL` 30) .|.
(safeCast seconds `shiftL` 24) .|.
safeCast nanosecHundreds
decodeCalendar :: Word60 -> CalendarTime
decodeCalendar w = CalendarTime
{ months = leastSignificant12 $ v `shiftR` 48
, days = leastSignificant6 $ v `shiftR` 42
, hours = leastSignificant6 $ v `shiftR` 36
, minutes = leastSignificant6 $ v `shiftR` 30
, seconds = leastSignificant6 $ v `shiftR` 24
, nanosecHundreds = leastSignificant24 v
}
where
v = safeCast w :: Word64
decodeTime :: Word64 -> Time
decodeTime value = Time $ value .&. 0xCFFFFFFFFFFFFFFF
encodeEvent :: Event -> UUID
encodeEvent Event{time, replica} =
UUID (varietyAndValue .|. originVariety) (eventVersion .|. origin)
where
Time varietyAndValue = time
(originVariety, origin) = encodeReplicaId replica
eventVersion = 0x2000000000000000
decodeEvent :: UUID -> Event
decodeEvent u@(UUID x _) = Event{replica = decodeReplica u, time = decodeTime x}
decodeReplica :: UUID -> Replica
decodeReplica (UUID x y) =
Replica $ (x .&. 0x3000000000000000) .|. (y .&. 0x0FFFFFFFFFFFFFFF)
encodeReplicaId :: Replica -> (Word64, Word64)
encodeReplicaId (Replica r) =
( r .&. 0x3000000000000000
, r .&. 0x0FFFFFFFFFFFFFFF
)
-- | Make a calendar timestamp from a date
mkCalendarDate
:: (Word16, Word16, Word8) -- ^ date as (year, month [1..12], day [1..])
-> Maybe CalendarTime
mkCalendarDate ymd = mkCalendarDateTime ymd (0, 0, 0)
-- | Make a calendar timestamp from a date and a day time
mkCalendarDateTime
:: (Word16, Word16, Word8) -- ^ date as (year, month [1..12], day [1..])
-> (Word8, Word8, Word8) -- ^ day time as (hours, minutes, seconds)
-> Maybe CalendarTime
mkCalendarDateTime ymd hms = mkCalendarDateTimeNano ymd hms 0
-- | Make a calendar timestamp from a date, a day time, and a second fraction
mkCalendarDateTimeNano
:: (Word16, Word16, Word8) -- ^ date as (year, month [1..12], day [1..])
-> (Word8, Word8, Word8) -- ^ day time as (hours, minutes, seconds)
-> Word32 -- ^ fraction of a second in hundreds of
-- nanosecond
-> Maybe CalendarTime
mkCalendarDateTimeNano (y, m, d) (hh, mm, ss) hns = do
guard $ y >= 2010
let months = (y - 2010) * 12 + m - 1
guard $ months < 4096
_ <- fromGregorianValid (fromIntegral y) (fromIntegral m) (fromIntegral d)
_ <-
makeTimeOfDayValid (fromIntegral hh) (fromIntegral mm) (fromIntegral ss)
guard $ hns < 10000000
pure CalendarTime
{ months = ls12 months
, days = ls6 $ d - 1
, hours = ls6 hh
, minutes = ls6 mm
, seconds = ls6 ss
, nanosecHundreds = ls24 hns
}
-- | Make a replica id from 'OriginVariety' and arbitrary number
mkReplica :: OriginVariety -> Word60 -> Replica
mkReplica (OriginVariety variety) origin =
Replica $ (safeCast variety `shiftL` 60) .|. safeCast origin
mkTime :: TimeVariety -> Word60 -> Time
mkTime (TimeVariety variety) value =
Time $ (safeCast variety `shiftL` 62) .|. safeCast value
mkCalendarEvent :: CalendarTime -> Replica -> Event
mkCalendarEvent time replica =
Event{time = mkTime Calendar $ encodeCalendar time, replica}