packages feed

calamity 0.1.5.1 → 0.1.6.0

raw patch · 8 files changed

+366/−238 lines, 8 filesdep +safe-exceptionsdep +unagi-chanPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: safe-exceptions, unagi-chan

API changes (from Hackage documentation)

- Calamity.Client.Client: [$sel:eventQueue:Client] :: Client -> TQueue DispatchMessage
- Calamity.Client.Types: [$sel:eventQueue:Client] :: Client -> TQueue DispatchMessage
- Calamity.Client.Types: instance GHC.Base.Monoid (Calamity.Client.Types.EventHandler d)
- Calamity.Client.Types: instance GHC.Base.Semigroup (Calamity.Client.Types.EventHandler d)
- Calamity.Gateway.DispatchEvents: DispatchData' :: DispatchData -> DispatchMessage
- Calamity.Gateway.DispatchEvents: data DispatchMessage
- Calamity.Gateway.DispatchEvents: instance GHC.Generics.Generic Calamity.Gateway.DispatchEvents.DispatchMessage
- Calamity.Gateway.DispatchEvents: instance GHC.Show.Show Calamity.Gateway.DispatchEvents.DispatchMessage
- Calamity.Gateway.Shard: [$sel:cmdQueue:Shard] :: Shard -> TQueue ControlMessage
- Calamity.Gateway.Shard: [$sel:evtQueue:Shard] :: Shard -> TQueue DispatchMessage
- Calamity.Gateway.Types: Dispatch :: Int -> !DispatchData -> ReceivedDiscordMessage
- Calamity.Gateway.Types: ShardExcRestart :: ShardException
- Calamity.Gateway.Types: ShardExcShutDown :: ShardException
- Calamity.Gateway.Types: [$sel:cmdQueue:Shard] :: Shard -> TQueue ControlMessage
- Calamity.Gateway.Types: [$sel:evtQueue:Shard] :: Shard -> TQueue DispatchMessage
- Calamity.Gateway.Types: data ShardException
- Calamity.Gateway.Types: instance GHC.Exception.Type.Exception Calamity.Gateway.Types.ShardException
- Calamity.Gateway.Types: instance GHC.Show.Show Calamity.Gateway.Types.ShardException
- Calamity.Gateway.Types: parseDispatchData :: DispatchType -> Value -> Parser DispatchData
+ Calamity.Client.Client: Dispatch :: DispatchData -> CalamityEvent
+ Calamity.Client.Client: ShutDown :: CalamityEvent
+ Calamity.Client.Client: [$sel:eventsIn:Client] :: Client -> InChan CalamityEvent
+ Calamity.Client.Client: [$sel:eventsOut:Client] :: Client -> OutChan CalamityEvent
+ Calamity.Client.Client: customEvt :: forall s a. (Typeable s, Typeable a) => a -> CalamityEvent
+ Calamity.Client.Client: data CalamityEvent
+ Calamity.Client.Client: events :: BotC r => Sem r (OutChan CalamityEvent)
+ Calamity.Client.Client: fire :: BotC r => CalamityEvent -> Sem r ()
+ Calamity.Client.Client: sendPresence :: BotC r => StatusUpdateData -> Sem r ()
+ Calamity.Client.Types: ChannelCreateEvt :: EventType
+ Calamity.Client.Types: ChannelDeleteEvt :: EventType
+ Calamity.Client.Types: ChannelUpdateEvt :: EventType
+ Calamity.Client.Types: ChannelpinsUpdateEvt :: EventType
+ Calamity.Client.Types: CustomEvt :: s -> a -> EventType
+ Calamity.Client.Types: GuildBanAddEvt :: EventType
+ Calamity.Client.Types: GuildBanRemoveEvt :: EventType
+ Calamity.Client.Types: GuildCreateEvt :: EventType
+ Calamity.Client.Types: GuildDeleteEvt :: EventType
+ Calamity.Client.Types: GuildEmojisUpdateEvt :: EventType
+ Calamity.Client.Types: GuildIntegrationsUpdateEvt :: EventType
+ Calamity.Client.Types: GuildMemberAddEvt :: EventType
+ Calamity.Client.Types: GuildMemberRemoveEvt :: EventType
+ Calamity.Client.Types: GuildMemberUpdateEvt :: EventType
+ Calamity.Client.Types: GuildMembersChunkEvt :: EventType
+ Calamity.Client.Types: GuildRoleCreateEvt :: EventType
+ Calamity.Client.Types: GuildRoleDeleteEvt :: EventType
+ Calamity.Client.Types: GuildRoleUpdateEvt :: EventType
+ Calamity.Client.Types: GuildUpdateEvt :: EventType
+ Calamity.Client.Types: MessageCreateEvt :: EventType
+ Calamity.Client.Types: MessageDeleteBulkEvt :: EventType
+ Calamity.Client.Types: MessageDeleteEvt :: EventType
+ Calamity.Client.Types: MessageReactionAddEvt :: EventType
+ Calamity.Client.Types: MessageReactionRemoveAllEvt :: EventType
+ Calamity.Client.Types: MessageReactionRemoveEvt :: EventType
+ Calamity.Client.Types: MessageUpdateEvt :: EventType
+ Calamity.Client.Types: ReadyEvt :: EventType
+ Calamity.Client.Types: TypingStartEvt :: EventType
+ Calamity.Client.Types: UserUpdateEvt :: EventType
+ Calamity.Client.Types: [$sel:eventsIn:Client] :: Client -> InChan CalamityEvent
+ Calamity.Client.Types: [$sel:eventsOut:Client] :: Client -> OutChan CalamityEvent
+ Calamity.Client.Types: class GetEventHandlers a m
+ Calamity.Client.Types: class InsertEventHandler a m
+ Calamity.Client.Types: data EventType
+ Calamity.Client.Types: getCustomEventHandlers :: TypeRep -> TypeRep -> EventHandlers -> [Dynamic]
+ Calamity.Client.Types: getEventHandlers :: GetEventHandlers a m => EventHandlers -> [EHType a m]
+ Calamity.Client.Types: instance (Calamity.Client.Types.EHInstanceSelector a GHC.Types.~ flag, Calamity.Client.Types.GetEventHandlers' flag a m) => Calamity.Client.Types.GetEventHandlers a m
+ Calamity.Client.Types: instance (Calamity.Client.Types.EHInstanceSelector a GHC.Types.~ flag, Calamity.Client.Types.InsertEventHandler' flag a m) => Calamity.Client.Types.InsertEventHandler a m
+ Calamity.Client.Types: instance (Data.Typeable.Internal.Typeable s, Calamity.Client.Types.EHStorageType s GHC.Types.~ [Data.Dynamic.Dynamic], Data.Typeable.Internal.Typeable (Calamity.Client.Types.EHType s m)) => Calamity.Client.Types.InsertEventHandler' 'GHC.Types.False s m
+ Calamity.Client.Types: instance (Data.Typeable.Internal.Typeable s, Data.Typeable.Internal.Typeable (Calamity.Client.Types.EHType s m), Calamity.Client.Types.EHStorageType s GHC.Types.~ [Data.Dynamic.Dynamic]) => Calamity.Client.Types.GetEventHandlers' 'GHC.Types.False s m
+ Calamity.Client.Types: instance GHC.Base.Monoid (Calamity.Client.Types.EventHandler t)
+ Calamity.Client.Types: instance GHC.Base.Semigroup (Calamity.Client.Types.EventHandler t)
+ Calamity.Client.Types: instance forall a1 s1 (a2 :: a1) (s2 :: s1) (m :: * -> *). (Data.Typeable.Internal.Typeable a2, Data.Typeable.Internal.Typeable s2, Data.Typeable.Internal.Typeable (Calamity.Client.Types.EHType ('Calamity.Client.Types.CustomEvt s2 a2) m)) => Calamity.Client.Types.GetEventHandlers' 'GHC.Types.True ('Calamity.Client.Types.CustomEvt s2 a2) m
+ Calamity.Client.Types: instance forall a1 s1 (a2 :: a1) (s2 :: s1) (m :: * -> *). (Data.Typeable.Internal.Typeable a2, Data.Typeable.Internal.Typeable s2, Data.Typeable.Internal.Typeable (Calamity.Client.Types.EHType ('Calamity.Client.Types.CustomEvt s2 a2) m)) => Calamity.Client.Types.InsertEventHandler' 'GHC.Types.True ('Calamity.Client.Types.CustomEvt s2 a2) m
+ Calamity.Client.Types: makeEventHandlers :: InsertEventHandler a m => Proxy a -> Proxy m -> EHType a m -> EventHandlers
+ Calamity.Gateway.DispatchEvents: Custom :: TypeRep -> Dynamic -> CalamityEvent
+ Calamity.Gateway.DispatchEvents: Dispatch :: DispatchData -> CalamityEvent
+ Calamity.Gateway.DispatchEvents: data CalamityEvent
+ Calamity.Gateway.DispatchEvents: instance GHC.Generics.Generic Calamity.Gateway.DispatchEvents.CalamityEvent
+ Calamity.Gateway.DispatchEvents: instance GHC.Show.Show Calamity.Gateway.DispatchEvents.CalamityEvent
+ Calamity.Gateway.Shard: [$sel:cmdOut:Shard] :: Shard -> OutChan ControlMessage
+ Calamity.Gateway.Shard: [$sel:evtIn:Shard] :: Shard -> InChan CalamityEvent
+ Calamity.Gateway.Types: EvtDispatch :: Int -> !DispatchData -> ReceivedDiscordMessage
+ Calamity.Gateway.Types: ShardFlowRestart :: ShardFlowControl
+ Calamity.Gateway.Types: ShardFlowShutDown :: ShardFlowControl
+ Calamity.Gateway.Types: [$sel:cmdOut:Shard] :: Shard -> OutChan ControlMessage
+ Calamity.Gateway.Types: [$sel:evtIn:Shard] :: Shard -> InChan CalamityEvent
+ Calamity.Gateway.Types: data ShardFlowControl
+ Calamity.Gateway.Types: instance GHC.Show.Show Calamity.Gateway.Types.ShardFlowControl
- Calamity.Client.Client: Client :: TVar [(Shard, Async (Maybe ()))] -> MVar Int -> Token -> RateLimitState -> TQueue DispatchMessage -> Client
+ Calamity.Client.Client: Client :: TVar [(InChan ControlMessage, Async (Maybe ()))] -> MVar Int -> Token -> RateLimitState -> InChan CalamityEvent -> OutChan CalamityEvent -> Client
- Calamity.Client.Client: [$sel:shards:Client] :: Client -> TVar [(Shard, Async (Maybe ()))]
+ Calamity.Client.Client: [$sel:shards:Client] :: Client -> TVar [(InChan ControlMessage, Async (Maybe ()))]
- Calamity.Client.Client: react :: forall (s :: Symbol) r. (KnownSymbol s, BotC r, EHType' s ~ Dynamic, Typeable (EHType s (Sem r))) => EHType s (Sem r) -> Sem r ()
+ Calamity.Client.Client: react :: forall s r. (BotC r, InsertEventHandler s (Sem r)) => EHType s (Sem r) -> Sem r ()
- Calamity.Client.Types: Client :: TVar [(Shard, Async (Maybe ()))] -> MVar Int -> Token -> RateLimitState -> TQueue DispatchMessage -> Client
+ Calamity.Client.Types: Client :: TVar [(InChan ControlMessage, Async (Maybe ()))] -> MVar Int -> Token -> RateLimitState -> InChan CalamityEvent -> OutChan CalamityEvent -> Client
- Calamity.Client.Types: EH :: [EHType' d] -> EventHandler d
+ Calamity.Client.Types: EH :: ((Semigroup (EHStorageType t), Monoid (EHStorageType t)) => EHStorageType t) -> EventHandler t
- Calamity.Client.Types: [$sel:shards:Client] :: Client -> TVar [(Shard, Async (Maybe ()))]
+ Calamity.Client.Types: [$sel:shards:Client] :: Client -> TVar [(InChan ControlMessage, Async (Maybe ()))]
- Calamity.Client.Types: [$sel:unwrapEventHandler:EH] :: EventHandler d -> [EHType' d]
+ Calamity.Client.Types: [$sel:unwrapEventHandler:EH] :: EventHandler t -> (Semigroup (EHStorageType t), Monoid (EHStorageType t)) => EHStorageType t
- Calamity.Client.Types: newtype EventHandler d
+ Calamity.Client.Types: newtype EventHandler t
- Calamity.Client.Types: type family EHType' d
+ Calamity.Client.Types: type family EHType (d :: EventType) m
- Calamity.Gateway.DispatchEvents: ShutDown :: DispatchMessage
+ Calamity.Gateway.DispatchEvents: ShutDown :: CalamityEvent
- Calamity.Gateway.Shard: Shard :: Int -> Int -> Text -> TQueue DispatchMessage -> TQueue ControlMessage -> TVar ShardState -> Text -> Shard
+ Calamity.Gateway.Shard: Shard :: Int -> Int -> Text -> InChan CalamityEvent -> OutChan ControlMessage -> IORef ShardState -> Text -> Shard
- Calamity.Gateway.Shard: [$sel:shardState:Shard] :: Shard -> TVar ShardState
+ Calamity.Gateway.Shard: [$sel:shardState:Shard] :: Shard -> IORef ShardState
- Calamity.Gateway.Shard: newShard :: Members '[LogEff, MetricEff, Embed IO, Final IO, Async] r => Text -> Int -> Int -> Token -> TQueue DispatchMessage -> Sem r (Shard, Async (Maybe ()))
+ Calamity.Gateway.Shard: newShard :: Members '[LogEff, MetricEff, Embed IO, Final IO, Async] r => Text -> Int -> Int -> Token -> InChan CalamityEvent -> Sem r (InChan ControlMessage, Async (Maybe ()))
- Calamity.Gateway.Types: Shard :: Int -> Int -> Text -> TQueue DispatchMessage -> TQueue ControlMessage -> TVar ShardState -> Text -> Shard
+ Calamity.Gateway.Types: Shard :: Int -> Int -> Text -> InChan CalamityEvent -> OutChan ControlMessage -> IORef ShardState -> Text -> Shard
- Calamity.Gateway.Types: [$sel:shardState:Shard] :: Shard -> TVar ShardState
+ Calamity.Gateway.Types: [$sel:shardState:Shard] :: Shard -> IORef ShardState
- Calamity.Gateway.Types: type ShardC r = Members '[LogEff, AtomicState ShardState, Embed IO, Final IO, Async, MetricEff] r
+ Calamity.Gateway.Types: type ShardC r = (Members '[LogEff, AtomicState ShardState, Embed IO, Final IO, Async, MetricEff] r)

Files

README.md view
@@ -110,7 +110,7 @@ main = do   token <- view packed <$> getEnv "BOT_TOKEN"   P.runFinal . P.embedToFinal . handleFailByPrinting . runCounterAtomic . runCacheInMemory . runMetricsNoop-    $ runBotIO (BotToken token) $ react @"messagecreate" $ \msg -> handleFailByLogging $ do+    $ runBotIO (BotToken token) $ react @'MessageCreateEvt $ \msg -> handleFailByLogging $ do       when (msg ^. #content == "!count") $ replicateM_ 3 $ do         val <- getCounter         info $ "the counter is: " <> showt val
calamity.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 486875ebdeafb710d8f23e4c4e501e85fa1ba7a0fbd9b4730097390055cfb514+-- hash: ff41b2035734187fe33f9394cd057e7ad8f187dc4e297c846472c876600e2d45  name:           calamity-version:        0.1.5.1+version:        0.1.6.0 synopsis:       A library for writing discord bots description:    Please see the README on GitHub at <https://github.com/nitros12/calamity#readme> category:       Network, Web@@ -137,6 +137,7 @@     , polysemy >=1.3 && <2     , polysemy-plugin >=0.2 && <0.3     , reflection >=2.1 && <3+    , safe-exceptions >=0.1 && <2     , scientific >=0.3 && <0.4     , stm >=2.5 && <3     , stm-chans >=3.0 && <4@@ -145,6 +146,7 @@     , text-show >=3.8 && <4     , time >=1.8 && <1.11     , typerep-map >=0.3 && <0.4+    , unagi-chan >=0.4 && <0.5     , unordered-containers >=0.2 && <0.3     , vector >=0.12 && <0.13     , websockets >=0.12 && <0.13
src/Calamity/Client/Client.hs view
@@ -3,9 +3,14 @@ -- | The client module Calamity.Client.Client     ( Client(..)+    , CalamityEvent(Dispatch, ShutDown)     , react     , runBotIO-    , stopBot ) where+    , stopBot+    , sendPresence+    , events+    , fire+    , customEvt ) where  import           Calamity.Cache.Eff import           Calamity.Client.ShardManager@@ -23,6 +28,7 @@ import           Calamity.Types.Snowflake import           Calamity.Types.Token +import           Control.Concurrent.Chan.Unagi import           Control.Concurrent.MVar import           Control.Concurrent.STM import           Control.Lens@@ -32,16 +38,15 @@ import           Data.Dynamic import           Data.Foldable import           Data.Maybe+import           Data.Proxy import           Data.Time.Clock.POSIX import           Data.Traversable-import qualified Data.TypeRepMap                  as TM+import           Data.Typeable  import qualified DiPolysemy                       as Di  import           Fmt -import           GHC.TypeLits- import qualified Polysemy                         as P import qualified Polysemy.Async                   as P import qualified Polysemy.AtomicState             as P@@ -49,7 +54,6 @@ import qualified Polysemy.Fail                    as P import qualified Polysemy.Reader                  as P - timeA :: P.Member (P.Embed IO) r => P.Sem r a -> P.Sem r (Double, a) timeA m = do   start <- P.embed getPOSIXTime@@ -64,13 +68,14 @@   shards'        <- newTVarIO []   numShards'     <- newEmptyMVar   rlState'       <- newRateLimitState-  eventQueue'    <- newTQueueIO+  (inc, outc)    <- newChan    pure $ Client shards'                 numShards'                 token                 rlState'-                eventQueue'+                inc+                outc  runBotIO :: (P.Members '[P.Embed IO, P.Final IO, P.Fail, CacheEff, MetricEff] r, Typeable r) => Token -> SetupEff r -> P.Sem r () runBotIO token setup = do@@ -82,39 +87,66 @@     clientLoop     finishUp -react :: forall (s :: Symbol) r. (KnownSymbol s, BotC r, EHType' s ~ Dynamic, Typeable (EHType s (P.Sem r))) => EHType s (P.Sem r) -> P.Sem r ()-react f =-  let handlers = EventHandlers . TM.one $ EH @s [toDyn f]-  in P.atomicModify (handlers <>)+react :: forall s r. (BotC r, InsertEventHandler s (P.Sem r)) => EHType s (P.Sem r) -> P.Sem r ()+react handler = let handlers = makeEventHandlers (Proxy @s) (Proxy @(P.Sem r)) handler+                in P.atomicModify (handlers <>) +fire :: BotC r => CalamityEvent -> P.Sem r ()+fire e = do+  inc <- P.asks (^. #eventsIn)+  P.embed $ writeChan inc e++-- | Build a Custom CalamityEvent+customEvt :: forall s a. (Typeable s, Typeable a) => a -> CalamityEvent+customEvt x = Custom (typeRep $ Proxy @s) (toDyn x)++events :: BotC r => P.Sem r (OutChan CalamityEvent)+events = do+  inc <- P.asks (^. #eventsIn)+  P.embed $ dupChan inc++sendPresence :: BotC r => StatusUpdateData -> P.Sem r ()+sendPresence s = do+  shards <- P.asks (^. #shards) >>= P.embed . readTVarIO+  for_ shards $ \(inc, _) ->+    P.embed $ writeChan inc (SendPresence s)+ stopBot :: BotC r => P.Sem r () stopBot = do   debug "stopping bot"   shards <- P.asks (^. #shards) >>= P.embed . readTVarIO-  for_ shards $ \shard ->-    P.embed . atomically $ writeTQueue (shard ^. _1 . #cmdQueue) ShutDownShard-  eventQueue <- P.asks (^. #eventQueue)-  P.embed . atomically $ writeTQueue eventQueue ShutDown+  for_ shards $ \(inc, _) ->+    P.embed $ writeChan inc ShutDownShard+  inc <- P.asks (^. #eventsIn)+  P.embed $ writeChan inc ShutDown  finishUp :: BotC r => P.Sem r () finishUp = do   debug "finishing up"   shards <- P.asks (^. #shards) >>= P.embed . readTVarIO-  for_ shards $ \shard -> void . P.await $ (shard ^. _2)+  for_ shards $ \(_, shardThread) -> P.await shardThread   debug "bot has stopped"  -- | main loop of the client, handles fetching the next event, processing the event -- and invoking it's handler functions clientLoop :: BotC r => P.Sem r () clientLoop = do-  evtQueue <- P.asks (^. #eventQueue)+  outc <- P.asks (^. #eventsOut)   void . P.runError . forever $ do-    evt' <- P.embed . atomically $ readTQueue evtQueue+    evt' <- P.embed $ readChan outc     case evt' of-      DispatchData' evt -> P.raise $ handleEvent evt-      ShutDown          -> P.throw ()+      Dispatch evt -> P.raise $ handleEvent evt+      Custom s d   -> handleCustomEvent s d+      ShutDown     -> P.throw ()   debug "leaving client loop" +handleCustomEvent :: forall r. BotC r => TypeRep -> Dynamic -> P.Sem r ()+handleCustomEvent s d = do+  debug "handling a custom event"+  eventHandlers <- P.atomicGet++  for_ (getCustomEventHandlers s (dynTypeRep d) eventHandlers) (\h -> fromJust . fromDynamic @(P.Sem r ()) $ dynApp h d)+ handleEvent :: BotC r => DispatchData -> P.Sem r () handleEvent data' = do   debug "handling an event"@@ -143,40 +175,32 @@ --       `r` where we handle events be: `(P.Error a ': r)`, which will make stuff explode when we unwrap the --       event handlers -unwrapEvent :: forall s r.-            (KnownSymbol s, EHType' s ~ Dynamic, Typeable r, Typeable (EHType s (P.Sem r)))-            => EventHandlers-            -> [EHType s (P.Sem r)]-unwrapEvent (EventHandlers eh) = map (fromJust . fromDynamic) . unwrapEventHandler @s . fromJust-  $ (TM.lookup eh :: Maybe (EventHandler s))--- where unwrapEach handler =---         let msg = "wanted: " <> show (typeRep $ Proxy @r) <> ", got: " <> show (dynTypeRep handler)---         in unwrapEvt msg . fromDynamic $ handler- handleEvent' :: BotC r               => EventHandlers               -> DispatchData               -> P.Sem (P.Fail ': r) [P.Sem r ()] handleEvent' eh evt@(Ready rd@ReadyData {}) = do   updateCache evt-  pure $ map ($ rd) (unwrapEvent @"ready" eh)+  pure $ map ($ rd) (getEventHandlers @'ReadyEvt eh) +handleEvent' _ Resumed = pure []+ handleEvent' eh evt@(ChannelCreate (DMChannel' chan)) = do   updateCache evt   Just newChan <- DMChannel' <<$>> getDM (getID chan)-  pure $ map ($ newChan) (unwrapEvent @"channelcreate" eh)+  pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh)  handleEvent' eh evt@(ChannelCreate (GuildChannel' chan)) = do   updateCache evt   Just guild <- getGuild (getID chan)   Just newChan <- pure $ GuildChannel' <$> guild ^. #channels . at (getID chan)-  pure $ map ($ newChan) (unwrapEvent @"channelcreate" eh)+  pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh)  handleEvent' eh evt@(ChannelUpdate (DMChannel' chan)) = do   Just oldChan <- DMChannel' <<$>> getDM (getID chan)   updateCache evt   Just newChan <- DMChannel' <<$>> getDM (getID chan)-  pure $ map (\f -> f oldChan newChan) (unwrapEvent @"channelupdate" eh)+  pure $ map (\f -> f oldChan newChan) (getEventHandlers @'ChannelUpdateEvt eh)  handleEvent' eh evt@(ChannelUpdate (GuildChannel' chan)) = do   Just oldGuild <- getGuild (getID chan)@@ -184,74 +208,74 @@   updateCache evt   Just newGuild <- getGuild (getID chan)   Just newChan <- pure $ GuildChannel' <$> newGuild ^. #channels . at (getID chan)-  pure $ map (\f -> f oldChan newChan) (unwrapEvent @"channelupdate" eh)+  pure $ map (\f -> f oldChan newChan) (getEventHandlers @'ChannelUpdateEvt eh)  handleEvent' eh evt@(ChannelDelete (GuildChannel' chan)) = do   Just oldGuild <- getGuild (getID chan)   Just oldChan <- pure $ GuildChannel' <$> oldGuild ^. #channels . at (getID chan)   updateCache evt-  pure $ map (\f -> f oldChan) (unwrapEvent @"channeldelete" eh)+  pure $ map (\f -> f oldChan) (getEventHandlers @'ChannelDeleteEvt eh)  handleEvent' eh evt@(ChannelDelete (DMChannel' chan)) = do   Just oldChan <- DMChannel' <<$>> getDM (getID chan)   updateCache evt-  pure $ map (\f -> f oldChan) (unwrapEvent @"channeldelete" eh)+  pure $ map (\f -> f oldChan) (getEventHandlers @'ChannelDeleteEvt eh)  -- handleEvent' eh evt@(ChannelPinsUpdate ChannelPinsUpdateData { channelID, lastPinTimestamp }) = do --   chan <- (GuildChannel' <$> os ^? #channels . at (coerceSnowflake channelID) . _Just) --     <|> (DMChannel' <$> os ^? #dms . at (coerceSnowflake channelID) . _Just)---   pure $ map (\f -> f chan lastPinTimestamp) (unwrapEvent @"channelpinsupdate" eh)+--   pure $ map (\f -> f chan lastPinTimestamp) (getEventHandlers @"channelpinsupdate" eh)  handleEvent' eh evt@(GuildCreate guild) = do   isNew <- isUnavailableGuild (getID guild)   updateCache evt   Just guild <- getGuild (getID guild)-  pure $ map (\f -> f guild isNew) (unwrapEvent @"guildcreate" eh)+  pure $ map (\f -> f guild isNew) (getEventHandlers @'GuildCreateEvt eh)  handleEvent' eh evt@(GuildUpdate guild) = do   Just oldGuild <- getGuild (getID guild)   updateCache evt   Just newGuild <- getGuild (getID guild)-  pure $ map (\f -> f oldGuild newGuild) (unwrapEvent @"guildupdate" eh)+  pure $ map (\f -> f oldGuild newGuild) (getEventHandlers @'GuildUpdateEvt eh)  -- NOTE: Guild will be deleted in the new cache if unavailable was false handleEvent' eh evt@(GuildDelete UnavailableGuild { id, unavailable }) = do   Just oldGuild <- getGuild id   updateCache evt-  pure $ map (\f -> f oldGuild unavailable) (unwrapEvent @"guilddelete" eh)+  pure $ map (\f -> f oldGuild unavailable) (getEventHandlers @'GuildDeleteEvt eh)  handleEvent' eh evt@(GuildBanAdd BanData { guildID, user }) = do   Just guild <- getGuild guildID   updateCache evt-  pure $ map (\f -> f guild user) (unwrapEvent @"guildbanadd" eh)+  pure $ map (\f -> f guild user) (getEventHandlers @'GuildBanAddEvt eh)  handleEvent' eh evt@(GuildBanRemove BanData { guildID, user }) = do   Just guild <- getGuild guildID   updateCache evt-  pure $ map (\f -> f guild user) (unwrapEvent @"guildbanremove" eh)+  pure $ map (\f -> f guild user) (getEventHandlers @'GuildBanRemoveEvt eh)  -- NOTE: we fire this event using the guild data with old emojis handleEvent' eh evt@(GuildEmojisUpdate GuildEmojisUpdateData { guildID, emojis }) = do   Just guild <- getGuild guildID   updateCache evt-  pure $ map (\f -> f guild emojis) (unwrapEvent @"guildemojisupdate" eh)+  pure $ map (\f -> f guild emojis) (getEventHandlers @'GuildEmojisUpdateEvt eh)  handleEvent' eh evt@(GuildIntegrationsUpdate GuildIntegrationsUpdateData { guildID }) = do   updateCache evt   Just guild <- getGuild guildID-  pure $ map ($ guild) (unwrapEvent @"guildintegrationsupdate" eh)+  pure $ map ($ guild) (getEventHandlers @'GuildIntegrationsUpdateEvt eh)  handleEvent' eh evt@(GuildMemberAdd member) = do   updateCache evt   Just guild <- getGuild (getID member)   Just member <- pure $ guild ^. #members . at (getID member)-  pure $ map ($ member) (unwrapEvent @"guildmemberadd" eh)+  pure $ map ($ member) (getEventHandlers @'GuildMemberAddEvt eh)  handleEvent' eh evt@(GuildMemberRemove GuildMemberRemoveData { user, guildID }) = do   Just guild <- getGuild guildID   Just member <- pure $ guild ^. #members . at (getID user)   updateCache evt-  pure $ map ($ member) (unwrapEvent @"guildmemberremove" eh)+  pure $ map ($ member) (getEventHandlers @'GuildMemberRemoveEvt eh)  handleEvent' eh evt@(GuildMemberUpdate GuildMemberUpdateData { user, guildID }) = do   Just oldGuild <- getGuild guildID@@ -259,19 +283,19 @@   updateCache evt   Just newGuild <- getGuild guildID   Just newMember <- pure $ newGuild ^. #members . at (getID user)-  pure $ map (\f -> f oldMember newMember) (unwrapEvent @"guildmemberupdate" eh)+  pure $ map (\f -> f oldMember newMember) (getEventHandlers @'GuildMemberUpdateEvt eh)  handleEvent' eh evt@(GuildMembersChunk GuildMembersChunkData { members, guildID }) = do   updateCache evt   Just guild <- getGuild guildID   let members' = guild ^.. #members . foldMap (at . getID) members . _Just-  pure $ map (\f -> f guild members') (unwrapEvent @"guildmemberschunk" eh)+  pure $ map (\f -> f guild members') (getEventHandlers @'GuildMembersChunkEvt eh)  handleEvent' eh evt@(GuildRoleCreate GuildRoleData { guildID, role }) = do   updateCache evt   Just guild <- getGuild guildID   Just role' <- pure $ guild ^. #roles . at (getID role)-  pure $ map (\f -> f guild role') (unwrapEvent @"guildrolecreate" eh)+  pure $ map (\f -> f guild role') (getEventHandlers @'GuildRoleCreateEvt eh)  handleEvent' eh evt@(GuildRoleUpdate GuildRoleData { guildID, role }) = do   Just oldGuild <- getGuild guildID@@ -279,50 +303,50 @@   updateCache evt   Just newGuild <- getGuild guildID   Just newRole <- pure $ newGuild ^. #roles . at (getID role)-  pure $ map (\f -> f newGuild oldRole newRole) (unwrapEvent @"guildroleupdate" eh)+  pure $ map (\f -> f newGuild oldRole newRole) (getEventHandlers @'GuildRoleUpdateEvt eh)  handleEvent' eh evt@(GuildRoleDelete GuildRoleDeleteData { guildID, roleID }) = do   Just guild <- getGuild guildID   Just role <- pure $ guild ^. #roles . at roleID   updateCache evt-  pure $ map (\f -> f guild role) (unwrapEvent @"guildroledelete" eh)+  pure $ map (\f -> f guild role) (getEventHandlers @'GuildRoleDeleteEvt eh)  handleEvent' eh evt@(MessageCreate msg) = do   messagesReceived <- registerCounter "messages_received" mempty   void $ addCounter 1 messagesReceived   updateCache evt-  pure $ map ($ msg) (unwrapEvent @"messagecreate" eh)+  pure $ map ($ msg) (getEventHandlers @'MessageCreateEvt eh)  handleEvent' eh evt@(MessageUpdate msg) = do   Just oldMsg <- getMessage (getID msg)   updateCache evt   Just newMsg <- getMessage (getID msg)-  pure $ map (\f -> f oldMsg newMsg) (unwrapEvent @"messageupdate" eh)+  pure $ map (\f -> f oldMsg newMsg) (getEventHandlers @'MessageUpdateEvt eh)  handleEvent' eh evt@(MessageDelete MessageDeleteData { id }) = do   Just oldMsg <- getMessage id   updateCache evt-  pure $ map ($ oldMsg) (unwrapEvent @"messagedelete" eh)+  pure $ map ($ oldMsg) (getEventHandlers @'MessageDeleteEvt eh)  handleEvent' eh evt@(MessageDeleteBulk MessageDeleteBulkData { ids }) = do   messages <- catMaybes <$> mapM getMessage ids   updateCache evt-  join <$> for messages (\msg -> pure $ map ($ msg) (unwrapEvent @"messagedelete" eh))+  join <$> for messages (\msg -> pure $ map ($ msg) (getEventHandlers @'MessageDeleteEvt eh))  handleEvent' eh evt@(MessageReactionAdd reaction) = do   updateCache evt   Just msg <- getMessage (getID reaction)-  pure $ map (\f -> f msg reaction) (unwrapEvent @"messagereactionadd" eh)+  pure $ map (\f -> f msg reaction) (getEventHandlers @'MessageReactionAddEvt eh)  handleEvent' eh evt@(MessageReactionRemove reaction) = do   Just msg <- getMessage (getID reaction)   updateCache evt-  pure $ map (\f -> f msg reaction) (unwrapEvent @"messagereactionremove" eh)+  pure $ map (\f -> f msg reaction) (getEventHandlers @'MessageReactionRemoveEvt eh)  handleEvent' eh evt@(MessageReactionRemoveAll MessageReactionRemoveAllData { messageID }) = do   Just msg <- getMessage messageID   updateCache evt-  pure $ map ($ msg) (unwrapEvent @"messagereactionremoveall" eh)+  pure $ map ($ msg) (getEventHandlers @'MessageReactionRemoveAllEvt eh)  handleEvent' eh evt@(PresenceUpdate PresenceUpdateData { userID, presence = Presence { guildID } }) = do   Just oldGuild <- getGuild guildID@@ -331,9 +355,9 @@   Just newGuild <- getGuild guildID   Just newMember <- pure $ newGuild ^. #members . at (coerceSnowflake userID)   let userUpdates = if oldMember ^. #user /= newMember ^. #user-                    then map (\f -> f (oldMember ^. #user) (newMember ^. #user)) (unwrapEvent @"userupdate" eh)+                    then map (\f -> f (oldMember ^. #user) (newMember ^. #user)) (getEventHandlers @'UserUpdateEvt eh)                     else mempty-  pure $ userUpdates <> map (\f -> f oldMember newMember) (unwrapEvent @"guildmemberupdate" eh)+  pure $ userUpdates <> map (\f -> f oldMember newMember) (getEventHandlers @'GuildMemberUpdateEvt eh)  handleEvent' eh (TypingStart TypingStartData { channelID, guildID, userID, timestamp }) =   case guildID of@@ -341,16 +365,16 @@       Just guild <- getGuild gid       Just member <- pure $ guild ^. #members . at (coerceSnowflake userID)       Just chan <- pure $ GuildChannel' <$> guild ^. #channels . at (coerceSnowflake channelID)-      pure $ map (\f -> f chan (Just member) timestamp) (unwrapEvent @"typingstart" eh)+      pure $ map (\f -> f chan (Just member) timestamp) (getEventHandlers @'TypingStartEvt eh)     Nothing -> do       Just chan <- DMChannel' <<$>> getDM (coerceSnowflake channelID)-      pure $ map (\f -> f chan Nothing timestamp) (unwrapEvent @"typingstart" eh)+      pure $ map (\f -> f chan Nothing timestamp) (getEventHandlers @'TypingStartEvt eh)  handleEvent' eh evt@(UserUpdate _) = do   Just oldUser <- getBotUser   updateCache evt   Just newUser <- getBotUser-  pure $ map (\f -> f oldUser newUser) (unwrapEvent @"userupdate" eh)+  pure $ map (\f -> f oldUser newUser) (getEventHandlers @'UserUpdateEvt eh)  handleEvent' _ e = fail $ "Unhandled event: " <> show e 
src/Calamity/Client/ShardManager.hs view
@@ -31,7 +31,7 @@   when hasShards $ fail "don't use shardBot on an already running bot."    token <- P.asks Calamity.Client.Types.token-  eventQueue <- P.asks eventQueue+  inc <- P.asks (^. #eventsIn)    Right gateway <- invoke GetGatewayBot @@ -42,7 +42,7 @@   info $ "Number of shards: " +| numShards' |+ ""    shards <- for [0 .. numShards' - 1] $ \id ->-    newShard host id numShards' token eventQueue+    newShard host id numShards' token inc    P.embed . atomically $ writeTVar shardsVar shards 
src/Calamity/Client/Types.hs view
@@ -4,16 +4,19 @@     , BotC     , SetupEff     , EHType-    , EHType'     , EventHandlers(..)-    , EventHandler(..) ) where+    , EventHandler(..)+    , InsertEventHandler(..)+    , GetEventHandlers(..)+    , EventType(..)+    , getCustomEventHandlers ) where -import           Calamity.Metrics.Eff import           Calamity.Cache.Eff-import           Calamity.Gateway.DispatchEvents-import           Calamity.Gateway.Shard+import           Calamity.Gateway.DispatchEvents ( CalamityEvent(..), ReadyData )+import           Calamity.Gateway.Types          ( ControlMessage ) import           Calamity.HTTP.Internal.Types import           Calamity.LogEff+import           Calamity.Metrics.Eff import           Calamity.Types.Model.Channel import           Calamity.Types.Model.Guild import           Calamity.Types.Model.User@@ -21,19 +24,23 @@ import           Calamity.Types.UnixTimestamp  import           Control.Concurrent.Async+import           Control.Concurrent.Chan.Unagi import           Control.Concurrent.MVar-import           Control.Concurrent.STM.TQueue import           Control.Concurrent.STM.TVar  import           Data.Default.Class import           Data.Dynamic+import           Data.Functor+import qualified Data.HashMap.Lazy               as LH+import           Data.Maybe import           Data.Time import qualified Data.TypeRepMap                 as TM import           Data.TypeRepMap                 ( TypeRepMap, WrapTypeable(..) )+import           Data.Typeable+import           Data.Void  import           GHC.Exts                        ( fromList ) import           GHC.Generics-import qualified GHC.TypeLits                    as TL  import qualified Polysemy                        as P import qualified Polysemy.Async                  as P@@ -41,11 +48,12 @@ import qualified Polysemy.Reader                 as P  data Client = Client-  { shards        :: TVar [(Shard, Async (Maybe ()))]+  { shards        :: TVar [(InChan ControlMessage, Async (Maybe ()))]   , numShards     :: MVar Int   , token         :: Token   , rlState       :: RateLimitState-  , eventQueue    :: TQueue DispatchMessage+  , eventsIn      :: InChan CalamityEvent+  , eventsOut     :: OutChan CalamityEvent   }   deriving ( Generic ) @@ -56,109 +64,146 @@  type SetupEff r = P.Sem (LogEff ': P.Reader Client ': P.AtomicState EventHandlers ': P.Async ': r) () -type family EHType d m where-  EHType "ready"                    m = ReadyData                                   -> m ()-  EHType "channelcreate"            m = Channel                                     -> m ()-  EHType "channelupdate"            m = Channel   -> Channel                        -> m ()-  EHType "channeldelete"            m = Channel                                     -> m ()-  EHType "channelpinsupdate"        m = Channel   -> Maybe UTCTime                  -> m ()-  EHType "guildcreate"              m = Guild     -> Bool                           -> m ()-  EHType "guildupdate"              m = Guild     -> Guild                          -> m ()-  EHType "guilddelete"              m = Guild     -> Bool                           -> m ()-  EHType "guildbanadd"              m = Guild     -> User                           -> m ()-  EHType "guildbanremove"           m = Guild     -> User                           -> m ()-  EHType "guildemojisupdate"        m = Guild     -> [Emoji]                        -> m ()-  EHType "guildintegrationsupdate"  m = Guild                                       -> m ()-  EHType "guildmemberadd"           m = Member                                      -> m ()-  EHType "guildmemberremove"        m = Member                                      -> m ()-  EHType "guildmemberupdate"        m = Member    -> Member                         -> m ()-  EHType "guildmemberschunk"        m = Guild     -> [Member]                       -> m ()-  EHType "guildrolecreate"          m = Guild     -> Role                           -> m ()-  EHType "guildroleupdate"          m = Guild     -> Role          -> Role          -> m ()-  EHType "guildroledelete"          m = Guild     -> Role                           -> m ()-  EHType "messagecreate"            m = Message                                     -> m ()-  EHType "messageupdate"            m = Message   -> Message                        -> m ()-  EHType "messagedelete"            m = Message                                     -> m ()-  EHType "messagedeletebulk"        m = [Message]                                   -> m ()-  EHType "messagereactionadd"       m = Message   -> Reaction                       -> m ()-  EHType "messagereactionremove"    m = Message   -> Reaction                       -> m ()-  EHType "messagereactionremoveall" m = Message                                     -> m ()-  EHType "typingstart"              m = Channel   -> Maybe Member  -> UnixTimestamp -> m ()-  EHType "userupdate"               m = User      -> User                           -> m ()-  EHType s _ = TL.TypeError ('TL.Text "Unknown event name: " 'TL.:<>: 'TL.ShowType s)-  -- EHType "voicestateupdate"         = VoiceStateUpdateData -> EventM ()-  -- EHType "voiceserverupdate"        = VoiceServerUpdateData -> EventM ()-  -- EHType "webhooksupdate"           = WebhooksUpdateData -> EventM ()+-- | A Data Kind used to fire custom events+data EventType+  = ReadyEvt+  | ChannelCreateEvt+  | ChannelUpdateEvt+  | ChannelDeleteEvt+  | ChannelpinsUpdateEvt+  | GuildCreateEvt+  | GuildUpdateEvt+  | GuildDeleteEvt+  | GuildBanAddEvt+  | GuildBanRemoveEvt+  | GuildEmojisUpdateEvt+  | GuildIntegrationsUpdateEvt+  | GuildMemberAddEvt+  | GuildMemberRemoveEvt+  | GuildMemberUpdateEvt+  | GuildMembersChunkEvt+  | GuildRoleCreateEvt+  | GuildRoleUpdateEvt+  | GuildRoleDeleteEvt+  | MessageCreateEvt+  | MessageUpdateEvt+  | MessageDeleteEvt+  | MessageDeleteBulkEvt+  | MessageReactionAddEvt+  | MessageReactionRemoveEvt+  | MessageReactionRemoveAllEvt+  | TypingStartEvt+  | UserUpdateEvt+  | forall s a. CustomEvt s a -type family EHType' d where-  EHType' "ready"                    = Dynamic-  EHType' "channelcreate"            = Dynamic-  EHType' "channelupdate"            = Dynamic-  EHType' "channeldelete"            = Dynamic-  EHType' "channelpinsupdate"        = Dynamic-  EHType' "guildcreate"              = Dynamic-  EHType' "guildupdate"              = Dynamic-  EHType' "guilddelete"              = Dynamic-  EHType' "guildbanadd"              = Dynamic-  EHType' "guildbanremove"           = Dynamic-  EHType' "guildemojisupdate"        = Dynamic-  EHType' "guildintegrationsupdate"  = Dynamic-  EHType' "guildmemberadd"           = Dynamic-  EHType' "guildmemberremove"        = Dynamic-  EHType' "guildmemberupdate"        = Dynamic-  EHType' "guildmemberschunk"        = Dynamic-  EHType' "guildrolecreate"          = Dynamic-  EHType' "guildroleupdate"          = Dynamic-  EHType' "guildroledelete"          = Dynamic-  EHType' "messagecreate"            = Dynamic-  EHType' "messageupdate"            = Dynamic-  EHType' "messagedelete"            = Dynamic-  EHType' "messagedeletebulk"        = Dynamic-  EHType' "messagereactionadd"       = Dynamic-  EHType' "messagereactionremove"    = Dynamic-  EHType' "messagereactionremoveall" = Dynamic-  EHType' "typingstart"              = Dynamic-  EHType' "userupdate"               = Dynamic-  EHType' s = TL.TypeError ('TL.Text "Unknown event name: " 'TL.:<>: 'TL.ShowType s)+type family EHType (d :: EventType) m where+  EHType 'ReadyEvt                    m = ReadyData                                   -> m ()+  EHType 'ChannelCreateEvt            m = Channel                                     -> m ()+  EHType 'ChannelUpdateEvt            m = Channel   -> Channel                        -> m ()+  EHType 'ChannelDeleteEvt            m = Channel                                     -> m ()+  EHType 'ChannelpinsUpdateEvt        m = Channel   -> Maybe UTCTime                  -> m ()+  EHType 'GuildCreateEvt              m = Guild     -> Bool                           -> m ()+  EHType 'GuildUpdateEvt              m = Guild     -> Guild                          -> m ()+  EHType 'GuildDeleteEvt              m = Guild     -> Bool                           -> m ()+  EHType 'GuildBanAddEvt              m = Guild     -> User                           -> m ()+  EHType 'GuildBanRemoveEvt           m = Guild     -> User                           -> m ()+  EHType 'GuildEmojisUpdateEvt        m = Guild     -> [Emoji]                        -> m ()+  EHType 'GuildIntegrationsUpdateEvt  m = Guild                                       -> m ()+  EHType 'GuildMemberAddEvt           m = Member                                      -> m ()+  EHType 'GuildMemberRemoveEvt        m = Member                                      -> m ()+  EHType 'GuildMemberUpdateEvt        m = Member    -> Member                         -> m ()+  EHType 'GuildMembersChunkEvt        m = Guild     -> [Member]                       -> m ()+  EHType 'GuildRoleCreateEvt          m = Guild     -> Role                           -> m ()+  EHType 'GuildRoleUpdateEvt          m = Guild     -> Role          -> Role          -> m ()+  EHType 'GuildRoleDeleteEvt          m = Guild     -> Role                           -> m ()+  EHType 'MessageCreateEvt            m = Message                                     -> m ()+  EHType 'MessageUpdateEvt            m = Message   -> Message                        -> m ()+  EHType 'MessageDeleteEvt            m = Message                                     -> m ()+  EHType 'MessageDeleteBulkEvt        m = [Message]                                   -> m ()+  EHType 'MessageReactionAddEvt       m = Message   -> Reaction                       -> m ()+  EHType 'MessageReactionRemoveEvt    m = Message   -> Reaction                       -> m ()+  EHType 'MessageReactionRemoveAllEvt m = Message                                     -> m ()+  EHType 'TypingStartEvt              m = Channel   -> Maybe Member  -> UnixTimestamp -> m ()+  EHType 'UserUpdateEvt               m = User      -> User                           -> m ()+  EHType ('CustomEvt s a)             m = a                                           -> m () +type family EHType' (d :: EventType) where+  EHType' 'ReadyEvt                    = Dynamic+  EHType' 'ChannelCreateEvt            = Dynamic+  EHType' 'ChannelUpdateEvt            = Dynamic+  EHType' 'ChannelDeleteEvt            = Dynamic+  EHType' 'ChannelpinsUpdateEvt        = Dynamic+  EHType' 'GuildCreateEvt              = Dynamic+  EHType' 'GuildUpdateEvt              = Dynamic+  EHType' 'GuildDeleteEvt              = Dynamic+  EHType' 'GuildBanAddEvt              = Dynamic+  EHType' 'GuildBanRemoveEvt           = Dynamic+  EHType' 'GuildEmojisUpdateEvt        = Dynamic+  EHType' 'GuildIntegrationsUpdateEvt  = Dynamic+  EHType' 'GuildMemberAddEvt           = Dynamic+  EHType' 'GuildMemberRemoveEvt        = Dynamic+  EHType' 'GuildMemberUpdateEvt        = Dynamic+  EHType' 'GuildMembersChunkEvt        = Dynamic+  EHType' 'GuildRoleCreateEvt          = Dynamic+  EHType' 'GuildRoleUpdateEvt          = Dynamic+  EHType' 'GuildRoleDeleteEvt          = Dynamic+  EHType' 'MessageCreateEvt            = Dynamic+  EHType' 'MessageUpdateEvt            = Dynamic+  EHType' 'MessageDeleteEvt            = Dynamic+  EHType' 'MessageDeleteBulkEvt        = Dynamic+  EHType' 'MessageReactionAddEvt       = Dynamic+  EHType' 'MessageReactionRemoveEvt    = Dynamic+  EHType' 'MessageReactionRemoveAllEvt = Dynamic+  EHType' 'TypingStartEvt              = Dynamic+  EHType' 'UserUpdateEvt               = Dynamic+  EHType' ('CustomEvt _ _)             = Dynamic+ newtype EventHandlers = EventHandlers (TypeRepMap EventHandler) -newtype EventHandler d = EH-  { unwrapEventHandler :: [EHType' d]+type family EHStorageType t where+  EHStorageType ('CustomEvt s a) = LH.HashMap TypeRep (LH.HashMap TypeRep [Dynamic])+  EHStorageType t                = [EHType' t]++newtype EventHandler t = EH+  { unwrapEventHandler :: (Semigroup (EHStorageType t), Monoid (EHStorageType t)) => EHStorageType t   }-  deriving newtype ( Semigroup, Monoid ) +instance Semigroup (EventHandler t) where+  EH a <> EH b = EH $ a <> b++instance Monoid (EventHandler t) where+  mempty = EH mempty+ instance Default EventHandlers where-  def = EventHandlers $ fromList [ WrapTypeable $ EH @"ready" []-                                 , WrapTypeable $ EH @"channelcreate" []-                                 , WrapTypeable $ EH @"channelupdate" []-                                 , WrapTypeable $ EH @"channeldelete" []-                                 , WrapTypeable $ EH @"channelpinsupdate" []-                                 , WrapTypeable $ EH @"guildcreate" []-                                 , WrapTypeable $ EH @"guildupdate" []-                                 , WrapTypeable $ EH @"guilddelete" []-                                 , WrapTypeable $ EH @"guildbanadd" []-                                 , WrapTypeable $ EH @"guildbanremove" []-                                 , WrapTypeable $ EH @"guildemojisupdate" []-                                 , WrapTypeable $ EH @"guildintegrationsupdate" []-                                 , WrapTypeable $ EH @"guildmemberadd" []-                                 , WrapTypeable $ EH @"guildmemberremove" []-                                 , WrapTypeable $ EH @"guildmemberupdate" []-                                 , WrapTypeable $ EH @"guildrolecreate" []-                                 , WrapTypeable $ EH @"guildroleupdate" []-                                 , WrapTypeable $ EH @"guildroledelete" []-                                 , WrapTypeable $ EH @"messagecreate" []-                                 , WrapTypeable $ EH @"messageupdate" []-                                 , WrapTypeable $ EH @"messagedelete" []-                                 , WrapTypeable $ EH @"messagedeletebulk" []-                                 , WrapTypeable $ EH @"messagereactionadd" []-                                 , WrapTypeable $ EH @"messagereactionremove" []-                                 , WrapTypeable $ EH @"messagereactionremoveall" []-                                 , WrapTypeable $ EH @"typingstart" []-                                 , WrapTypeable $ EH @"userupdate" []-                                 -- , WrapTypeable $ EH m @"voicestateupdate" []-                                 -- , WrapTypeable $ EH m @"voiceserverupdate" []-                                 -- , WrapTypeable $ EH m @"webhooksupdate" []+  def = EventHandlers $ fromList [ WrapTypeable $ EH @'ReadyEvt []+                                 , WrapTypeable $ EH @'ChannelCreateEvt []+                                 , WrapTypeable $ EH @'ChannelUpdateEvt []+                                 , WrapTypeable $ EH @'ChannelDeleteEvt []+                                 , WrapTypeable $ EH @'ChannelpinsUpdateEvt []+                                 , WrapTypeable $ EH @'GuildCreateEvt []+                                 , WrapTypeable $ EH @'GuildUpdateEvt []+                                 , WrapTypeable $ EH @'GuildDeleteEvt []+                                 , WrapTypeable $ EH @'GuildBanAddEvt []+                                 , WrapTypeable $ EH @'GuildBanRemoveEvt []+                                 , WrapTypeable $ EH @'GuildEmojisUpdateEvt []+                                 , WrapTypeable $ EH @'GuildIntegrationsUpdateEvt []+                                 , WrapTypeable $ EH @'GuildMemberAddEvt []+                                 , WrapTypeable $ EH @'GuildMemberRemoveEvt []+                                 , WrapTypeable $ EH @'GuildMemberUpdateEvt []+                                 , WrapTypeable $ EH @'GuildMembersChunkEvt []+                                 , WrapTypeable $ EH @'GuildRoleCreateEvt []+                                 , WrapTypeable $ EH @'GuildRoleUpdateEvt []+                                 , WrapTypeable $ EH @'GuildRoleDeleteEvt []+                                 , WrapTypeable $ EH @'MessageCreateEvt []+                                 , WrapTypeable $ EH @'MessageUpdateEvt []+                                 , WrapTypeable $ EH @'MessageDeleteEvt []+                                 , WrapTypeable $ EH @'MessageDeleteBulkEvt []+                                 , WrapTypeable $ EH @'MessageReactionAddEvt []+                                 , WrapTypeable $ EH @'MessageReactionRemoveEvt []+                                 , WrapTypeable $ EH @'MessageReactionRemoveAllEvt []+                                 , WrapTypeable $ EH @'TypingStartEvt []+                                 , WrapTypeable $ EH @'UserUpdateEvt []+                                 , WrapTypeable $ EH @('CustomEvt Void Void) LH.empty                                  ]  instance Semigroup EventHandlers where@@ -166,3 +211,55 @@  instance Monoid EventHandlers where   mempty = def++-- not sure what to think of this++type family EHInstanceSelector (d :: EventType) :: Bool where+  EHInstanceSelector ('CustomEvt _ _) = 'True+  EHInstanceSelector _                = 'False++class InsertEventHandler a m where+  makeEventHandlers :: Proxy a -> Proxy m -> EHType a m -> EventHandlers++instance (EHInstanceSelector a ~ flag, InsertEventHandler' flag a m) => InsertEventHandler a m where+  makeEventHandlers = makeEventHandlers' (Proxy @flag)++class InsertEventHandler' (flag :: Bool) a m where+  makeEventHandlers' :: Proxy flag -> Proxy a -> Proxy m -> EHType a m -> EventHandlers++instance (Typeable a, Typeable s, Typeable (EHType ('CustomEvt s a) m))+  => InsertEventHandler' 'True ('CustomEvt s a) m where+  makeEventHandlers' _ _ _ handler = EventHandlers . TM.one $ EH @('CustomEvt Void Void)+    (LH.singleton (typeRep $ Proxy @s) (LH.singleton (typeRep $ Proxy @a) [toDyn handler]))++instance (Typeable s, EHStorageType s ~ [Dynamic], Typeable (EHType s m)) => InsertEventHandler' 'False s m where+  makeEventHandlers' _ _ _ handler = EventHandlers . TM.one $ EH @s [toDyn handler]+++class GetEventHandlers a m where+  getEventHandlers :: EventHandlers -> [EHType a m]++instance (EHInstanceSelector a ~ flag, GetEventHandlers' flag a m) => GetEventHandlers a m where+  getEventHandlers = getEventHandlers' (Proxy @a) (Proxy @m) (Proxy @flag)++class GetEventHandlers' (flag :: Bool) a m where+  getEventHandlers' :: Proxy a -> Proxy m -> Proxy flag -> EventHandlers -> [EHType a m]++instance (Typeable a, Typeable s, Typeable (EHType ('CustomEvt s a) m)) => GetEventHandlers' 'True ('CustomEvt s a) m where+  getEventHandlers' _ _ _ (EventHandlers handlers) =+    let handlerMap = unwrapEventHandler @('CustomEvt Void Void) $ fromJust+          (TM.lookup handlers :: Maybe (EventHandler ('CustomEvt Void Void)))+    in concat $ LH.lookup (typeRep $ Proxy @s) handlerMap >>= LH.lookup (typeRep $ Proxy @a) <&> map+       (fromJust . fromDynamic)++instance (Typeable s, Typeable (EHType s m), EHStorageType s ~ [Dynamic]) => GetEventHandlers' 'False s m where+  getEventHandlers' _ _ _ (EventHandlers handlers) =+    let theseHandlers = unwrapEventHandler @s $ fromJust (TM.lookup handlers :: Maybe (EventHandler s))+    in map (fromJust . fromDynamic) theseHandlers+++getCustomEventHandlers :: TypeRep -> TypeRep -> EventHandlers -> [Dynamic]+getCustomEventHandlers s a (EventHandlers handlers) =+    let handlerMap = unwrapEventHandler @('CustomEvt Void Void) $ fromJust+          (TM.lookup handlers :: Maybe (EventHandler ('CustomEvt Void Void)))+    in concat $ LH.lookup s handlerMap >>= LH.lookup a
src/Calamity/Gateway/DispatchEvents.hs view
@@ -17,14 +17,17 @@ import           Calamity.Types.UnixTimestamp  import           Data.Aeson+import           Data.Dynamic import           Data.Text.Lazy                              ( Text ) import           Data.Time+import           Data.Typeable import           Data.Vector.Unboxed                         ( Vector )  import           GHC.Generics -data DispatchMessage-  = DispatchData' DispatchData+data CalamityEvent+  = Dispatch DispatchData+  | Custom TypeRep Dynamic   | ShutDown   deriving ( Show, Generic ) 
src/Calamity/Gateway/Shard.hs view
@@ -15,6 +15,7 @@  import           Control.Concurrent import           Control.Concurrent.Async+import qualified Control.Concurrent.Chan.Unagi as UC import           Control.Concurrent.STM import           Control.Concurrent.STM.TBMQueue import           Control.Exception@@ -25,6 +26,7 @@ import qualified Data.Aeson                      as A import           Data.Functor import           Data.Maybe+import           Data.IORef import           Data.Text.Lazy                  ( Text, stripPrefix ) import           Data.Text.Lazy.Lens import           Data.Void@@ -42,7 +44,7 @@ import qualified Polysemy.AtomicState            as P import qualified Polysemy.Error                  as P import qualified Polysemy.Resource               as P-+import qualified Control.Exception.Safe as Ex import           Prelude                         hiding ( error )  import           TextShow@@ -80,21 +82,21 @@          -> Int          -> Int          -> Token-         -> TQueue DispatchMessage-         -> Sem r (Shard, Async (Maybe ()))-newShard gateway id count token evtQueue = do-  (shard, stateVar) <- P.embed $ mdo-    cmdQueue' <- newTQueueIO-    stateVar <- newTVarIO (newShardState shard)-    let shard = Shard id count gateway evtQueue cmdQueue' stateVar (rawToken token)-    pure (shard, stateVar)+         -> UC.InChan CalamityEvent+         -> Sem r (UC.InChan ControlMessage, Async (Maybe ()))+newShard gateway id count token evtIn = do+  (cmdIn, stateVar) <- P.embed $ mdo+    (cmdIn, cmdOut) <- UC.newChan+    stateVar <- newIORef $ newShardState shard+    let shard = Shard id count gateway evtIn cmdOut stateVar (rawToken token)+    pure (cmdIn, stateVar) -  let runShard = P.runAtomicStateTVar stateVar shardLoop+  let runShard = P.runAtomicStateIORef stateVar shardLoop   let action = attr "shard-id" id . push "calamity-shard" $ runShard    thread' <- P.async action -  pure (shard, thread')+  pure (cmdIn, thread')  sendToWs :: ShardC r => SentDiscordMessage -> Sem r () sendToWs data' = do@@ -110,17 +112,6 @@ fromEitherVoid (Left a) = a fromEitherVoid (Right a) = absurd a -- yeet --- | Catches ws close events and decides if we can restart or not-checkWSClose :: IO a -> IO (Either ControlMessage a)-checkWSClose m = (Right <$> m) `catch` \case-  e@(CloseRequest code _) -> do-    print e-    if code `elem` [1000, 4004, 4010, 4011]-      then pure . Left $ ShutDownShard-      else pure . Left $ RestartShard--  e                       -> throwIO e- tryWriteTBMQueue' :: TBMQueue a -> a -> STM Bool tryWriteTBMQueue' q v = do   v' <- tryWriteTBMQueue q v@@ -141,18 +132,23 @@   controlStream :: Shard -> TBMQueue ShardMsg -> IO ()   controlStream shard outqueue = inner     where-      q = shard ^. #cmdQueue+      q = shard ^. #cmdOut       inner = do-        v <- atomically $ readTQueue q+        v <- UC.readChan q         r <- atomically $ tryWriteTBMQueue' outqueue (Control v)         when r inner -  discordStream :: P.Members '[LogEff, MetricEff, P.Embed IO] r => Connection -> TBMQueue ShardMsg -> Sem r ()+  handleWSException :: SomeException -> IO (Either ControlMessage a)+  handleWSException e = pure $ case fromException e of+    Just (CloseRequest code _)+      | code `elem` [1000, 4004, 4010, 4011] ->+        Left ShutDownShard+    _ -> Left RestartShard++  discordStream :: P.Members '[LogEff, MetricEff, P.Embed IO, P.Final IO] r => Connection -> TBMQueue ShardMsg -> Sem r ()   discordStream ws outqueue = inner     where inner = do-            msg <- P.embed . checkWSClose $ receiveData ws--            -- trace $ "Received from stream: "+||msg||+""+            msg <- P.embed $ Ex.catchAny (Right <$> receiveData ws) handleWSException              case msg of               Left c ->@@ -168,15 +164,11 @@                     pure True                 when r inner -  -- mergedStream ::  Log -> Shard -> Connection -> ExceptT ShardException ShardM ShardMsg-  -- mergedStream logEnv shard ws =-  --   liftIO (fromEither <$> race (controlStream shard) (discordStream logEnv ws))-   -- | The outer loop, sets up the ws conn, etc handles reconnecting and such   -- Currently if this goes to the error path we just exit the forever loop   -- and the shard stops, maybe we might want to do some extra logic to reboot   -- the shard, or maybe force a resharding-  outerloop :: ShardC r => Sem r (Either ShardException ())+  outerloop :: ShardC r => Sem r (Either ShardFlowControl ())   outerloop = P.runError . forever $ do     shard :: Shard <- P.atomicGets (^. #shardS)     let host = shard ^. #gateway@@ -187,17 +179,17 @@     innerLoopVal <- websocketToIO $ runWebsocket host' "/?v=7&encoding=json" innerloop      case innerLoopVal of-      ShardExcShutDown -> do+      ShardFlowShutDown -> do         info "Shutting down shard"-        P.throw ShardExcShutDown+        P.throw ShardFlowShutDown -      ShardExcRestart ->+      ShardFlowRestart ->         info "Restaring shard"         -- we restart normally when we loop    -- | The inner loop, handles receiving a message from discord or a command message   -- and then decides what to do with it-  innerloop :: ShardC r => Connection -> Sem r ShardException+  innerloop :: ShardC r => Connection -> Sem r ShardFlowControl   innerloop ws = do     debug "Entering inner loop of shard" @@ -232,7 +224,7 @@      receivedMessages <- registerCounter "received_messages" [("shard", showt $ shard ^. #shardID)] -    result <- P.runResource $ P.bracket (P.embed $ newTBMQueueIO 1)+    result <- P.resourceToIOFinal $ P.bracket (P.embed $ newTBMQueueIO 1)       (P.embed . atomically . closeTBMQueue)       (\q -> do         debug "handling events now"@@ -251,9 +243,9 @@     pure result    -- | Handlers for each message, not sure what they'll need to do exactly yet-  handleMsg :: (ShardC r, P.Member (P.Error ShardException) r) => ShardMsg -> Sem r ()+  handleMsg :: (ShardC r, P.Member (P.Error ShardFlowControl) r) => ShardMsg -> Sem r ()   handleMsg (Discord msg) = case msg of-    Dispatch sn data' -> do+    EvtDispatch sn data' -> do       -- trace $ "Handling event: ("+||data'||+")"       P.atomicModify (#seqNum ?~ sn) @@ -264,9 +256,7 @@         _ -> pure ()        shard <- P.atomicGets (^. #shardS)-      P.embed . atomically $ writeTQueue (shard ^. #evtQueue) (DispatchData' data')-      -- sn' <- P.atomicGets (^. #seqNum)-      -- trace $ "Done handling event, seq is now: "+||sn'||+""+      P.embed $ UC.writeChan (shard ^. #evtIn) (Dispatch data')      HeartBeatReq -> do       debug "Received heartbeat request"@@ -274,7 +264,7 @@      Reconnect -> do       debug "Being asked to restart by Discord"-      P.throw ShardExcRestart+      P.throw ShardFlowRestart      InvalidSession resumable -> do       if resumable@@ -285,7 +275,7 @@         P.embed $ threadDelay (15 * 1000 * 1000)       else         info "Received resumable invalid session"-      P.throw ShardExcRestart+      P.throw ShardFlowRestart      Hello interval -> do       info $ "Received hello, beginning to heartbeat at an interval of "+|interval|+"ms"@@ -300,8 +290,8 @@       debug $ "Sending presence: ("+||data'||+")"       sendToWs $ StatusUpdate data' -    RestartShard       -> P.throw ShardExcRestart-    ShutDownShard      -> P.throw ShardExcShutDown+    RestartShard       -> P.throw ShardFlowRestart+    ShutDownShard      -> P.throw ShardFlowShutDown  startHeartBeatLoop :: ShardC r => Int -> Sem r () startHeartBeatLoop interval = do
src/Calamity/Gateway/Types.hs view
@@ -1,5 +1,19 @@ -- | Types for shards-module Calamity.Gateway.Types where+module Calamity.Gateway.Types+    ( ShardC+    , ShardMsg(..)+    , ReceivedDiscordMessage(..)+    , SentDiscordMessage(..)+    , DispatchType(..)+    , IdentifyData(..)+    , StatusUpdateData(..)+    , ResumeData(..)+    , RequestGuildMembersData(..)+    , IdentifyProps(..)+    , ControlMessage(..)+    , ShardFlowControl(..)+    , Shard(..)+    , ShardState(..) ) where  import           Calamity.Gateway.DispatchEvents import           Calamity.Internal.AesonThings@@ -10,13 +24,12 @@ import           Calamity.Types.Snowflake  import           Control.Concurrent.Async-import           Control.Concurrent.STM.TQueue-import           Control.Concurrent.STM.TVar-import           Control.Exception+import           Control.Concurrent.Chan.Unagi  import           Data.Aeson import qualified Data.Aeson.Types                 as AT import           Data.Generics.Labels             ()+import           Data.IORef import           Data.Maybe import           Data.Text.Lazy                   ( Text ) @@ -28,8 +41,8 @@ import qualified Polysemy.Async                   as P import qualified Polysemy.AtomicState             as P -type ShardC r = P.Members '[LogEff, P.AtomicState ShardState, P.Embed IO, P.Final IO,-  P.Async, MetricEff] r+type ShardC r = (P.Members '[LogEff, P.AtomicState ShardState, P.Embed IO, P.Final IO,+  P.Async, MetricEff] r)  data ShardMsg   = Discord ReceivedDiscordMessage@@ -37,7 +50,7 @@   deriving ( Show, Generic )  data ReceivedDiscordMessage-  = Dispatch Int !DispatchData+  = EvtDispatch Int !DispatchData   | HeartBeatReq   | Reconnect   | InvalidSession Bool@@ -53,7 +66,7 @@         d <- v .: "d"         t <- v .: "t"         s <- v .: "s"-        Dispatch s <$> parseDispatchData t d+        EvtDispatch s <$> parseDispatchData t d        1  -> pure HeartBeatReq @@ -231,19 +244,18 @@   | SendPresence StatusUpdateData   deriving ( Show ) -data ShardException-  = ShardExcRestart-  | ShardExcShutDown+data ShardFlowControl+  = ShardFlowRestart+  | ShardFlowShutDown   deriving ( Show )-  deriving anyclass Exception  data Shard = Shard   { shardID     :: Int   , shardCount  :: Int   , gateway     :: Text-  , evtQueue    :: TQueue DispatchMessage-  , cmdQueue    :: TQueue ControlMessage-  , shardState  :: TVar ShardState+  , evtIn       :: InChan CalamityEvent+  , cmdOut      :: OutChan ControlMessage+  , shardState  :: IORef ShardState   , token       :: Text   }   deriving ( Generic )