packages feed

mischief-ecs-0.1.0.0: src/Mischief/ECS/Graph.hs

{- HLINT ignore "Use second" -}
module Mischief.ECS.Graph where

import Control.Monad
import Data.Foldable
import Data.IORef
import Data.List qualified as List
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe
import Data.Set (Set)
import Data.Set qualified as Set
import Mischief.ECS.Vec (IOVec)
import Mischief.ECS.Vec qualified as Vec

data Graph a = Graph {nodes :: IORef (Map a Int), counter :: IORef Int, edges :: IOVec (Maybe a, Set Int)}

newGraph :: forall a. IO (Graph a)
newGraph = do
  nodes <- newIORef Map.empty
  counter <- newIORef 0
  edges <- Vec.new 16

  return Graph {nodes, counter, edges}

addNode :: (Ord a) => a -> Graph a -> IO Int
addNode node Graph {nodes, counter, edges} = do
  maybeNode <- tryGetNode node Graph {nodes, counter, edges}
  case maybeNode of
    Just x -> do
      setNode node x Graph {nodes, counter, edges}
      return x
    Nothing -> do
      index <- readIORef counter
      modifyIORef' counter (+ 1)
      Vec.pushBack edges (Just node, Set.empty)
      modifyIORef' nodes (Map.insert node index)
      return index

setNode :: a -> Int -> Graph a -> IO ()
setNode node index Graph {edges} = do
  Vec.modify_ edges index (\(_, x) -> (Just node, x))

tryGetNode :: (Ord a) => a -> Graph a -> IO (Maybe Int)
tryGetNode node graph = do
  nodes <- readIORef graph.nodes
  return $ Map.lookup node nodes

-- data GetOrAddResult = AddedNode Int | GotNode Int

getOrAddNode :: (Ord a) => a -> Graph a -> IO Int
getOrAddNode node graph = do
  nodes' <- readIORef graph.nodes
  case Map.lookup node nodes' of
    Just x -> return x
    Nothing -> do
      index <- readIORef graph.counter
      modifyIORef' graph.counter (+ 1)
      Vec.pushBack graph.edges (Nothing, Set.empty)
      modifyIORef' graph.nodes (Map.insert node index)
      return index

addEdge :: (Ord a) => (a, a) -> Graph a -> IO ()
addEdge (a, b) graph = do
  a' <- getOrAddNode a graph
  b' <- getOrAddNode b graph
  Vec.modify_ graph.edges b' (\(x, l) -> (x, Set.insert a' l))

takeRemoveableNodes :: IOVec (Maybe a, Set Int) -> IO [a]
takeRemoveableNodes edges = do
  edgeList <- Vec.toList edges
  len <- Vec.length edges
  let nodes = concatMap unwrap (filter (\((a, x), _) -> null x && isJust a) (zip edgeList [0 :: Int ..]))
  for_ nodes $ \(_, i) -> Vec.modify_ edges i (\(_, l) -> (Nothing, l))
  for_ nodes $ \(_, i) -> for_ [0 .. len - 1] $ \j -> Vec.modify_ edges j (\(a, l) -> (a, Set.delete i l))
  return (map fst nodes)
  where
    unwrap ((Just a, _), i) = [(a, i)]
    unwrap _ = []

getNodes :: Graph a -> IO [[a]]
getNodes Graph {edges} = do
  edges' <- Vec.clone edges
  res <- newIORef []
  step res edges'
  readIORef res
  where
    step res edges' = do
      nodes <- takeRemoveableNodes edges'
      unless (null nodes) $ do
        modifyIORef' res (++ [nodes])
        step res edges'