-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support a generic "original" payload in structure recognizer #2216
Conversation
69e4b82
to
3c4bc19
Compare
a204a0f
to
f863adc
Compare
b627c4d
to
fb8ea0d
Compare
@kostmo I am having trouble finding which modules contain the "main" changes (as opposed to other modules which "just" have to be updated to match the new types/definitions). Could you mention which modules are the interesting ones to look at? |
fb8ea0d
to
e5decbf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@byorgey I've annotated the most relevant changes now.
symmetryAnnotatedGrids <- mapM checkSymmetry recognizableGrids | ||
-- We exclude empty grids from the recognition engine. | ||
nonEmptyRecognizableGrids = mapMaybe (traverse getNonEmptyGrid) recognizableGrids | ||
|
||
myAutomatons <- | ||
either (fail . T.unpack . renderRedundancy) return $ | ||
mkAutomatons (fmap cellToEntity) nonEmptyRecognizableGrids |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@byorgey here is one interesting change. Previously the orientations to be recognized were checked for redundancy (according to symmetry) in the scenario parser. I have now moved that detail to within the recognizer API.
Related to this, we now instantiate the recognizer engine from within the scenario parsing code (instead of from within game state initialization), so that it can throw an error when a redundant recognition spec is provided.
where | ||
rotatedGrids = concatMap (extractGrids extractor . namedGrid) xs | ||
extractedItems = map (uncurry ExtractedArea . fmap (extractor . structure) . dupe) rawGrids |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another major change: the "extraction" of Entities
from Cells
(which, from the perspective of the recognizer library, is the opaque type parameter b
) is now only performed once in this single location, and the result is preserved and shared elsewhere it is needed. The "extraction" function also used to operate piecewise on each cell within a structure. Now, its contract is to take an entire "shape" of type b
and produce a NonEmptyGrid (Maybe a)
.
Maybe (StructureWithGrid (Maybe b) a) | ||
extractOrientedGrid extractor x d = | ||
case extractor <$> structure x of | ||
EmptyGrid -> Nothing | ||
Grid neGrid -> | ||
let w = RowWidth . rectWidth . getNEGridDimensions $ neGrid | ||
in Just $ | ||
StructureWithGrid wrapped d w $ | ||
applyOrientationTransformNE (Orientation d False) neGrid | ||
StructureWithGrid b a | ||
extractOrientedGrid (ExtractedArea x neGrid) d = | ||
StructureWithGrid d w $ | ||
ExtractedArea x $ | ||
applyOrientationTransformNE (Orientation d False) neGrid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This got much simpler since I pushed the enforcement of NonEmptyGrid
s further upstream.
x <- extractOrientedGrid extractor sGrid d | ||
return $ PositionedStructure (Cosmic subworldName loc) x | ||
return $ | ||
PositionedStructure (Cosmic subworldName loc) $ | ||
extractOrientedGrid (grid $ annotatedGrid sGrid) d | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the other place that "extraction" was recomputed. Now we just re-use the result computed earlier.
definitionMap = M.fromList $ map ((name &&& id) . namedGrid) structDefs | ||
definitionMap = theAutomatons ^. originalStructureDefinitions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also no longer need to recompute this Map
.
data StaticStructureInfo b = StaticStructureInfo | ||
{ _structureDefs :: [SymmetryAnnotatedGrid (Maybe b)] | ||
, _staticPlacements :: Map SubworldName [LocatedStructure] | ||
} | ||
deriving (Show) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This StaticStructureInfo
record was relocated to the Swarm.Game.Scenario.Topography.Structure.Recognition.Type
module. And its _structureDefs
member is made redundant.
doAddition newEntity r = do | ||
stateRevision <- case HM.lookup newEntity entLookup of | ||
Nothing -> return oldRecognitionState | ||
Just finder -> do | ||
tell . pure . FoundParticipatingEntity $ | ||
ParticipatingEntity | ||
newEntity | ||
(finder ^. inspectionOffsets) | ||
registerRowMatches entLoader cLoc finder oldRecognitionState | ||
|
||
return $ r & recognitionState .~ stateRevision | ||
where | ||
oldRecognitionState = r ^. recognitionState | ||
entLookup = autoRecognizer ^. automatonsByEntity | ||
|
||
doRemoval = do | ||
doAddition newEntity = | ||
maybe return logAndRegister $ HM.lookup newEntity entLookup | ||
where | ||
logAndRegister finder s = do | ||
tell . pure . FoundParticipatingEntity $ | ||
ParticipatingEntity | ||
newEntity | ||
(finder ^. inspectionOffsets) | ||
registerRowMatches entLoader cLoc finder s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is just some independent refactoring to clean up this logic
data ExtractedArea b a = ExtractedArea | ||
{ originalItem :: NamedArea b | ||
, extractedGrid :: NonEmptyGrid (AtomicKeySymbol a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new ExtractedArea
type is what propagates the original b
through the algorithm.
data NamedOriginal b = NamedOriginal | ||
{ getName :: StructureName | ||
, orig :: NamedGrid b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need NamedOriginal
anymore; we just access the name
through the NamedArea
record.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, the additional comments were very helpful! I think this looks good --- I'm always in favor of nice refactoring that makes things simpler + more modular!
@@ -20,7 +20,7 @@ import Swarm.Game.Universe (Cosmic) | |||
|
|||
renderSharedNames :: ConsolidatedRowReferences b a -> NonEmpty StructureName | |||
renderSharedNames = | |||
NE.nub . NE.map (getName . originalDefinition . wholeStructure) . referencingRows | |||
NE.nub . NE.map (name . originalItem . entityGrid . wholeStructure) . referencingRows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know the call to NE.nub
wasn't added or changed in this PR, but I guess I didn't notice it before. Is it a reasonable guarantee that the list of things here will be small? I ask since NE.nub
takes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most likely the list is small (it's bounded by the number of user-defined structures/their sizes). I was aware of this Data.List.NonEmpty
. However, now that I look, I see Data.List.NonEmpty.Extra.nubOrd
, so I'll use that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, nubOrd
uses a custom red-black tree implementation. I wonder why it doesn't just use Set
. Well, it doesn't matter. Since the list we are nubbing is just a list of names, nubOrd
should work well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the red-black implementation possibly be order-preserving?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder why it doesn't just use
Set
.
I presume it is to minimize dependencies of the extra
package. Particularly, extra
does not depend on containers
.
Incidentally, now that I've read the code I see that nubOrd
is order-preserving whereas nubSort
is not. That fact would be good to note explicitly in the docstring (rather than indirectly by example).
e5decbf
to
063dbc5
Compare
063dbc5
to
e2094ff
Compare
The recognizer algorithm operates on a
NonEmptyGrid
ofMaybe
elements. However, there may exist some progenitor object with auxiliary metadata payload that is independent of the algorithm but should be presented along with the final recognized structure.So long as an "extraction" function is provided that derives the nonempty grid of
a
s from the original objectb
, we can sendb
along for the ride through the algorithm without concern tob
's internals.Changes
Overall this refactoring makes the code more generic and simpler.
_structureDefs
member ofStaticStructureInfo
record with_staticAutomatons
fieldCell
toEntity
further up the pipelineShow
instances (show
-ing a Scenario is not likely to be useful due to sheer volume of content)