Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo committed Nov 12, 2024
1 parent 5f2ae54 commit 9a6837a
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
{-# LANGUAGE OverloadedStrings #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Types strictly for debugging structure recognition via the web interface
module Swarm.Game.Scenario.Topography.Structure.Recognition.Log where

import Data.Aeson
import Data.Int (Int32)
import Data.IntSet.NonEmpty (NEIntSet)
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NE
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics (Generic)
import Linear (V2)
import Servant.Docs (ToSample)
import Servant.Docs qualified as SD
import Swarm.Game.Location (Location)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
import Swarm.Game.Universe (Cosmic)
import Swarm.Language.Syntax.Direction (AbsoluteDir)

-- | Type aliases for documentation
type StructureRowContent e = SymbolSequence e

type WorldRowContent e = SymbolSequence e

data OrientedStructure = OrientedStructure
{ oName :: OriginalName
, oDir :: AbsoluteDir
Expand All @@ -31,6 +28,10 @@ data OrientedStructure = OrientedStructure
distillLabel :: StructureWithGrid b a -> OrientedStructure
distillLabel swg = OrientedStructure (getName $ originalDefinition swg) (rotatedTo swg)

renderSharedNames :: ConsolidatedRowReferences b a -> Text
renderSharedNames =
T.intercalate "/" . NE.toList . NE.nub . NE.map (getName . originalDefinition . wholeStructure) . referencingRows

newtype EntityKeyedFinder = EntityKeyedFinder
{ searchOffsets :: InspectionOffsets
}
Expand Down Expand Up @@ -73,39 +74,10 @@ data WrongRecurrenceCountExplanation = WrongRecurrenceCountExplanation
}
deriving (Generic, ToJSON)

-- | The located occurrences of a specific contiguous chunk of entities.
-- Note that an identical chunk may recur more than once in a structure row.
-- This record represents all of the recurrences of one such chunk.
--
-- Any different chunks contained within a row will be described by
-- their own instance of this record.
--
-- Note: By virtue of the searching algorithm, these indices
-- are expected to automatically be in sorted order
data FoundAndExpectedChunkPositions = FoundAndExpectedChunkPositions
{ foundPositions :: NEIntSet
, expectedPositions :: NEIntSet
}
deriving (Generic, ToJSON)

data FoundRowFromChunk a = FoundRowFromChunk
{ chunkOffsetFromSearchBorder :: Int
, rowIndexWithinStructure :: Int32
, structurePositionOffset :: V2 Int32
, chunkStructure :: a
}
deriving (Functor, Generic, ToJSON)

data ChunkedRowMatch a e = ChunkedRowMatch
{ positionsComparison :: [(FoundAndExpectedChunkPositions, NonEmpty e)]
, foundChunkRow :: FoundRowFromChunk a
}
deriving (Functor, Generic, ToJSON)

data SearchLog e
= FoundParticipatingEntity (ParticipatingEntity e)
| StructureRemoved OriginalName
| FoundCompleteStructureCandidates [OrientedStructure]
| FoundCompleteStructureCandidates [(OrientedStructure, Cosmic Location)]
| -- | this is actually internally used as a (Map (NonEmpty e) (NonEmpty Int)),
-- but the requirements of Functor force us to invert the mapping
FoundPiecewiseChunks [(NonEmpty Int, NonEmpty e)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,21 +145,24 @@ lookupStaticPlacements extractor (StaticStructureInfo structDefs thePlacements)
-- | Matches definitions against the placements.
-- Fails fast (short-circuits) if a non-matching
-- cell is encountered.
--
-- Returns 'Nothing' if there is no discrepancy between the match subject and world content.
-- Returns the first observed mismatch cell otherwise.
ensureStructureIntact ::
(Monad s, Hashable a) =>
GenericEntLocator s a ->
FoundStructure b a ->
s Bool
ensureStructureIntact entLoader (FoundStructure (StructureWithGrid _ _ _ grid) upperLeft) =
allM outer $ zip [0 ..] grid
ensureStructureIntact entLoader (FoundStructure (StructureWithGrid _ _ _ grid) upperLeft) = do
allM checkLoc allLocPairs
where
outer (y, row) = allM (inner y) $ zip [0 ..] row
inner y (x, maybeTemplateEntity) = case maybeTemplateEntity of
checkLoc (loc, maybeTemplateEntity) = case maybeTemplateEntity of
Nothing -> return True
Just _ ->
fmap (== maybeTemplateEntity) $
entLoader $
mkLoc x y
Just _ -> (== maybeTemplateEntity) <$> entLoader loc

allLocPairs = concatMap outer $ zip [0 ..] grid
outer (y, row) = zipWith (curry $ inner y) [0 ..] row
inner y (x, maybeTemplateEntity) = (mkLoc x y, maybeTemplateEntity)

-- NOTE: We negate the yOffset because structure rows are numbered increasing from top
-- to bottom, but swarm world coordinates increase from bottom to top.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ mkOffsets pos (RowWidth w) =
mkEntityLookup ::
(Hashable a, Eq a) =>
[StructureWithGrid b a] ->
HM.HashMap a (AutomatonNewInfo b a)
HM.HashMap a (AutomatonInfo b a)
mkEntityLookup grids =
HM.map mkRowAutomatons rowsByEntityParticipation
where
-- Produces an automaton to evaluate whenever a given entity
-- is encountered.
mkRowAutomatons neList =
AutomatonNewInfo bounds $
AutomatonInfo bounds $
PiecewiseRecognition smPiecewise extractedChunksForLookup
where
bounds = sconcat $ NE.map expandedOffsets neList
Expand All @@ -70,8 +70,6 @@ mkEntityLookup grids =
HS.fromList . concat . NE.toList $
NE.map (map chunkContents . contiguousChunks) neList

-- TODO: These should be grouped by
-- row content to avoid redundant checks
extractedChunksForLookup = NE.map f neList
where
f x = RowChunkMatchingReference (myRow x) (mkRightMap x)
Expand All @@ -91,6 +89,13 @@ mkEntityLookup grids =
binTuplesHM
. map (myEntity &&& id)
. concatMap explodeRowEntities
$ structureRowsByContent

structureRowsByContent =
map (\(x, y) -> ConsolidatedRowReferences x y . gridWidth . wholeStructure $ NE.head y)
. HM.toList
. binTuplesHM
. map (rowContent &&& id)
$ allStructureRows grids

getContiguousChunks :: [Maybe a] -> [PositionedChunk a]
Expand All @@ -109,9 +114,9 @@ getContiguousChunks rowMembers =
-- are dropped but accounted for positionally when indexing the columns.
explodeRowEntities ::
(Hashable a, Eq a) =>
StructureRow b a ->
ConsolidatedRowReferences b a ->
[SingleRowEntityOccurrences b a]
explodeRowEntities annotatedRow@(StructureRow _ _ rowMembers) =
explodeRowEntities annotatedRow@(ConsolidatedRowReferences rowMembers _ rw) =
map f $ HM.toList $ binTuplesHM unconsolidatedEntityOccurrences
where
chunks = getContiguousChunks rowMembers
Expand All @@ -130,8 +135,8 @@ explodeRowEntities annotatedRow@(StructureRow _ _ rowMembers) =
zipWith (\idx -> fmap (PositionWithinRow idx annotatedRow,)) [0 ..] rowMembers

deriveEntityOffsets :: PositionWithinRow b a -> InspectionOffsets
deriveEntityOffsets (PositionWithinRow pos r) =
mkOffsets pos $ gridWidth $ wholeStructure r
deriveEntityOffsets (PositionWithinRow pos _) =
mkOffsets pos rw

-- * Util

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ entityModified entLoader modification cLoc recognizer = do
stateRevision <- case HM.lookup newEntity entLookup of
Nothing -> return oldRecognitionState
Just finder -> do
let logFinder f =
EntityKeyedFinder
(f ^. inspectionOffsets2)
let logFinder f = EntityKeyedFinder (f ^. inspectionOffsets)
msg =
FoundParticipatingEntity $
ParticipatingEntity newEntity $
Expand Down Expand Up @@ -170,17 +168,22 @@ checkChunksCombination

let candidateExpected = concatMap NE.toList candidateExpectedLists
tell . pure . ChunksMatchingExpected $
map (modifyChunkedRowMatch $ fmap $ getName . originalDefinition . wholeStructure) candidateExpected
map (modifyChunkedRowMatch $ fmap renderSharedNames) candidateExpected

let structurePositionsToCheck = map mkFoundStructure candidateExpected
let structurePositionsToCheck = concatMap mkFoundStructures candidateExpected
filterM validateIntactness2d structurePositionsToCheck
where
foundRowChunksLookup = fmap NEIS.fromList $ binTuplesHM $ map (pVal &&& pIndex) candidatesChunked

mkFoundStructure x =
FoundStructure
(wholeStructure $ chunkStructure $ foundChunkRow x)
(cLoc `offsetBy` structurePositionOffset (foundChunkRow x))
mkFoundStructures x =
NE.toList $ NE.map mkFoundStructure . referencingRows . chunkStructure $ foundChunkRow x
where
mkFoundStructure r =
FoundStructure
(wholeStructure r)
(cLoc `offsetBy` theOffset)
where
theOffset = V2 (horizontalStructPos $ foundChunkRow x) (rowIndex r)

validateIntactness2d fs = do
intactness <- lift $ ensureStructureIntact entLoader fs
Expand All @@ -192,7 +195,7 @@ checkChunksCombination
return intactness

subsetChecker (RowChunkMatchingReference r chunkPositionMap) =
left (ChunkMatchFailureReason $ getName . originalDefinition . wholeStructure $ r) $ do
left (ChunkMatchFailureReason $ renderSharedNames r) $ do
let isKeysSubset = referenceChunksKeys `HS.isSubsetOf` foundChunksKeys
unless isKeysSubset . Left $
NoKeysSubset $
Expand Down Expand Up @@ -225,7 +228,7 @@ checkChunksCombination
mkRowMatch theIntersection rowOffset =
ChunkedRowMatch
(map swap $ HM.toList theIntersection)
(FoundRowFromChunk rowOffset (rowIndex r) (V2 horizontalStructurePosition (rowIndex r)) r)
(FoundRowFromChunk rowOffset horizontalStructurePosition r)
where
horizontalStructurePosition = fromIntegral rowOffset + getMin (startOffset horizontalOffsets)

Expand All @@ -241,10 +244,10 @@ registerRowMatches ::
(Monad s, Hashable a, Eq b) =>
GenericEntLocator s a ->
Cosmic Location ->
AutomatonNewInfo b a ->
AutomatonInfo b a ->
RecognitionState b a ->
WriterT [SearchLog a] s (RecognitionState b a)
registerRowMatches entLoader cLoc (AutomatonNewInfo horizontalOffsets pwMatcher) rState = do
registerRowMatches entLoader cLoc (AutomatonInfo horizontalOffsets pwMatcher) rState = do
entitiesRow <-
lift $
getWorldRow
Expand All @@ -256,7 +259,7 @@ registerRowMatches entLoader cLoc (AutomatonNewInfo horizontalOffsets pwMatcher)

tell $ pure $ StartSearchAt cLoc horizontalOffsets

tell . pure . ExpectedChunks $ NE.map (HM.keys . confirmationMap) pwMaps
tell . pure . ExpectedChunks $ NE.map (HM.keys . confirmationMap) rowChunkReferences

let candidatesChunked = findAll pwSM entitiesRow

Expand All @@ -265,14 +268,14 @@ registerRowMatches entLoader cLoc (AutomatonNewInfo horizontalOffsets pwMatcher)
registerStructureMatches intactStructuresLists rState
where
registry = rState ^. foundStructures
PiecewiseRecognition pwSM pwMaps = pwMatcher
PiecewiseRecognition pwSM rowChunkReferences = pwMatcher

checkCombo =
checkChunksCombination
entLoader
cLoc
horizontalOffsets
pwMaps
rowChunkReferences

-- | We only have to do this once, for the "smallest" chunk occurrence size discrepancy.
-- All subsequent chunks will merely filter on this initial set.
Expand Down Expand Up @@ -316,5 +319,5 @@ registerStructureMatches unrankedCandidates oldState = do
-- Sorted by decreasing order of preference.
rankedCandidates = sortOn Down unrankedCandidates

getStructInfo (FoundStructure swg _) = distillLabel swg
getStructInfo (FoundStructure swg loc) = (distillLabel swg, loc)
newMsg = FoundCompleteStructureCandidates $ map getStructInfo rankedCandidates
Loading

0 comments on commit 9a6837a

Please sign in to comment.