Closed cpennington closed 5 years ago
Maybe something like this?
import Apecs
import qualified Data.Set as S
data Flags
= Active
| SomeOtherFlag
| SomeThirdFlag
deriving (Eq, Show, Ord)
newtype FlagSet = FlagSet (S.Set Flags)
instance Component FlagSet where type Storage FlagSet = Map FlagSet
makeWorld "World" [ ''FlagSet ]
cmapFlags :: (Get World IO cx, Set World IO cy) => [Flags] -> (cx -> cy) -> System World ()
cmapFlags flags f = cmap $ \(FlagSet set,x) ->
if all (`S.member` set) flags
then Right (f x)
else Left ()
setFlag :: Entity -> Flags -> System World ()
setFlag ety flag = do
mset <- get ety
set ety . FlagSet $ case mset of
Just (FlagSet set) -> S.insert flag set
Nothing -> S.singleton flag
unsetFlag :: Entity -> Flags -> System World ()
unsetFlag ety flag = do
mset <- get ety
set ety $ case mset of
Nothing -> Nothing
Just (FlagSet set) ->
let set' = S.delete flag set
in if null set' then Nothing else Just (FlagSet set')
cmapFlags
is then cmap
that only operates on entities with the required flags. You can also consider representing the flag set as a bit vector.
Yeah, that makes sense...
Maybe I've had naive assumptions about the specifics of apecs
performance. Would that behave significantly differently from having each flag as a separate component, when it comes to iterating over the entities that have that component?
For instance, if there are 1000 entities, 1 of which has FlagA
and FlagB
, and 100 have FlagB
, does cmap \(FlagA) -. ...
iterate over roughly 1 item? Or does it iterate over all 1000, and only call the function on the 1?
cmap $ \FlagA -> ...
will iterate over everything that has a FlagA
cmap $ \(FlagA,FlagB) -> ...
will iterate over everything that has a FlagA
, and then filter out everything that does not have a FlagB
, thereby operating on everything that has both. So if FlagA
is unique, it will iterate over just the one.
But to answer your question, yes, this cmapFlags
would definitely be slower, since it now iterates over ever entity that has a FlagSet
, which would be most/all of them. On the other hand, I know that many modern game engines do essentially this using a bitmask/bit vector, and they're still plenty fast. Maybe it's worth making a specialized store for that at some point.
A third option might actually be to make the Template Haskell functions a bit more flexible. makeWorldAndComponents
already exists, we could expose the part that generates the Component
instances:
data FlagA = FlagA deriving ...
data FlagB = FlagB deriving ...
flags = [''FlagA, ''FlagB]
makeDefaultInstances flags
makeWorld "World" (flags ++ [ ''OtherComponent1, ... ]
Maybe it even makes sense to generate the data declarations themselves?
This is now possible in 4dc1bec5ea939c82419a5e7b1018add5ce9839b8
I'm working on an application where I want to be able to tag entities with a number of boolean properties. Thus far, I've been adding code like:
which then lets me do
set e Active
and to pattern match onActive
incmap
calls (for instance). However, it would be nice to not need to implement an instance for each new boolean flag that I add.One possibility that I had thought of was to use
DataKinds
and have a type-level string for each of the boolean properties that I want to tag entities with. However, I can't figure out if there's any reasonable way to satisfyHas w m c
without writing a new instance forProxy "mysymbol"
each time.Are there any other obvious ways to accomplish what I'm trying to accomplish?