jonascarpay / apecs Goto Github PK
View Code? Open in Web Editor NEWa fast, extensible, type driven Haskell ECS framework for games
a fast, extensible, type driven Haskell ECS framework for games
Is there a reason cmapMIf
and cmapM_If
are not implemented (other than: they didn't seem necessary, or nobody has needed them yet)?
I just tried implementing cmapM_If
myself, but maybe they should actually be included in the library itself? What do you think?
Here's my attempt at an implementation for cmapM_If
(It doesn't compile though, and I'm too much of a haskell beginner to fix it...):
cmapM_If :: forall w m cp c.
( Get w m cp
, Get w m c
, Members w m c
)
=> (cp -> Bool)
-> (c -> SystemT w m ())
-> SystemT w m ()
cmapM_If cond sys = do
sp :: Storage cp <- getStore
s :: Storage c <- getStore
sl <- lift $ explMembers (s, sp)
U.forM_ sl $ \ety -> do
p <- explGet sp ety
when (cond p) $ do
x <- lift $ explGet s ety
sys x
Assuming I want to get rid of the entity completely (say a decal went out of screen), how would I do it? AFAIU destroy
just removes a given component.
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:
data Active = Active
deriving (Show, Generic, Flat, Eq)
instance Component Active where
type Storage Active = Map Active
which then lets me do set e Active
and to pattern match on Active
in cmap
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 satisfy Has w m c
without writing a new instance for Proxy "mysymbol"
each time.
Are there any other obvious ways to accomplish what I'm trying to accomplish?
Here's it in its full glory (=
-- | Applies a function, if possible.
{-# INLINE modify #-}
modify, ($~) :: forall w m cx cy. (Get w m cx, Set w m cy) => Entity -> (cx -> cy) -> SystemT w m ()
modify (Entity ety) f = do
sx :: Storage cx <- getStore
sy :: Storage cy <- getStore
lift$ do
x <- explGet sx ety
explSet sy ety (f x)
I don't know what "if possible" could mean here, since explGet on
Mapstore crashes and needs an additional
Maybe` wrapper.
Adding explExists
check before getting sx
helps, but I'm concerned about performance if the fix is applied by default. The additional check should be pretty cheap though, in O(min(n,W)) as stated by IntMap
.
I propose adding unsafeModify
which skips the check and moving the default to the new non-crashing wrapper.
Alternatively, it's a documentation issue and just the description has to be corrected.
Relying on $~
to do the right thing may or may not be a code smell leading to time spent on debugging "I do this thing but nothing happens" due to misplaced assumptions.
What do you think?
Is there a community Gitter, Discord, etc.? Would be cool to talk to people who are using this!
I cannot get the collision handlers in apecs-physics to work. I tried the following, however the collision callback never gets executed. I am using the latest versions of both apecs and apecs-phyics from hackage.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Apecs
import Apecs.Physics
import Control.Concurrent
import Control.Monad
data Moving = Moving
instance Component Moving where
type Storage Moving = Unique Moving
makeWorld "World" [''Physics, ''Moving]
initialize :: System World ()
initialize = do
movingBody <- newEntity (Moving, DynamicBody, Position (V2 0 0), Velocity (V2 30 0))
movingShape <- newEntity
(Shape movingBody $ cRectangle $ V2 10 10, CollisionFilter 1 (maskList [1]) (maskList [2]), Mass 10)
obstacleBody <- newEntity (StaticBody, Position (V2 100 0))
obstacleShape <- newEntity
(Shape obstacleBody $ cRectangle $ V2 10 10, CollisionFilter 2 (maskList [2]) (maskList [1]))
handler <- createCollisionHandler
newEntity handler
pure ()
createCollisionHandler :: System World CollisionHandler
createCollisionHandler = do
begin <- createBeginHandler
pure $ CollisionHandler (Between 1 2) (Just begin) Nothing Nothing Nothing
where
createBeginHandler = mkBeginCB $ \_ -> do
liftIO $ print "It worked!"
pure True
step :: System World ()
step = do
cmapM_ $ \(Moving, Position pos) -> liftIO (print pos)
stepPhysics (1/60)
main :: IO ()
main = initWorld >>= \w -> runWith w $ do
initialize
forever $ step >> liftIO (threadDelay (1000000 `quot` 60))
Curiously enough, replacing (Between 1 2)
with (Wildcard 0)
results in the callback getting run two times and is therefore working as intended.
Can anybody give me a hint?
I'm having a very difficult time figuring out what's causing this exception. As far as I can tell, it's coming from here, which I guess means the entity is getting double-deleted somehow. I've tried everything I can to get this working but I'm not sure where I go from here. Even if I check that the components exist for the entity that I'm deleting before I delete them it still throws the exception.
The relevant part of my code is here, specifically the call to the destroy
function. If I comment that out, it works fine, and it only throws an exception when the function is called from handleCollisions
, not clampPlayer
. Pardon the code, I'm new to both ECS and game programming in general.
Building library for apecs-stm-0.1.4..
[1 of 2] Compiling Apecs.STM
/tmp/stack-c53d58dfa7c9502e/apecs-stm-0.1.4/src/Apecs/STM.hs:43:62: error:
Module ‘Apecs.TH’ does not export ‘makeMapComponentsFor’
|
43 | import Apecs.TH (makeWorldNoEC, makeMapComponentsFor)
| ^^^^^^^^^^^^^^^^^^^^
And apecs-0.9.3 is not on Hackage.
-- While building package apecs-physics-0.4.5 (scroll up to its section to see the error) using:
/var/stackage/.stack/programs/x86_64-linux/ghc-tinfo6-9.4.3/bin/ghc-9.4.3 --make -odir /var/stackage/work/unpack-dir/unpacked/
apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d100b410f347f4beb3e281e35462/.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.8.
1.0/setup -hidir /var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d100b410f347f4beb3e2
81e35462/.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.8.1.0/setup -i -i. -clear-package-db -global-package-db -package-db=/var/stack
age/.stack/snapshots/x86_64-linux-tinfo6/fc84417690be23f4858027e8a7aaa300225a04393aaeb93a40c10aa8df38e07a/9.4.3/pkgdb -package-db=/v
ar/stackage/work/unpack-dir/.stack-work/install/x86_64-linux-tinfo6/fc84417690be23f4858027e8a7aaa300225a04393aaeb93a40c10aa8df38e07a
/9.4.3/pkgdb -hide-all-packages -package-id=Cabal-3.8.1.0 -package-id=base-4.17.0.0 -optP-include -optP/var/stackage/work/unpack-dir
/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d100b410f347f4beb3e281e35462/.stack-work/dist/x86_64-linux-tinfo6/
Cabal-3.8.1.0/setup/setup_macros.h /var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d1
00b410f347f4beb3e281e35462/Setup.hs /var/stackage/.stack/setup-exe-src/setup-shim-mPHDZzAJ.hs -main-is StackSetupShim.mainOverride -
o /var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d100b410f347f4beb3e281e35462/.stack
-work/dist/x86_64-linux-tinfo6/Cabal-3.8.1.0/setup/setup -threaded
Process exited with code: ExitFailure 1
Logs have been written to: /var/stackage/work/unpack-dir/.stack-work/logs/apecs-physics-0.4.5.log
[1 of 3] Compiling Main ( /var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a923931
1d100b410f347f4beb3e281e35462/Setup.hs, /var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239
311d100b410f347f4beb3e281e35462/.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.8.1.0/setup/Main.o )
/var/stackage/work/unpack-dir/unpacked/apecs-physics-0.4.5-73728b2ca8d874a4f142c957dd81a9239311d100b410f347f4beb3e281e35462/Setup.hs
:14:17: error:
Variable not in scope:
readGenericPackageDescription
:: Verbosity -> String -> IO GenericPackageDescription
Suggested fix:
Perhaps use one of these:
data constructor ‘GenericPackageDescription’ (imported from Distribution.PackageDescription),
‘emptyGenericPackageDescription’ (imported from Distribution.PackageDescription),
‘parseGenericPackageDescription’ (imported from Distribution.PackageDescription.Parsec)
|
14 | pkgDescr <- readGenericPackageDescription verbose "apecs-physics.cabal"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I'm not entirely sure what the best way to do this would be, but it would be nice to have travis check whether the examples build against the current apecs version.
the following code seems like it should work, but crashes at runtime:
cmapM_ $ \(HasLocation _ _, entity) -> destroy entity (Proxy :: Proxy HasLocation)
with an error like Reading non-existent Map component HasLocation for entity 89
am i missing something? (in this case of course HasLocation
is both the type and the constructor of that same type)
Map
from apecs conflicts with Map
from Data.Map
(and anything that exports it)
This is minor, but it would be nice to be able to use apecs and protolude without
any conflicts.
I don't think this necessitates a new release or anything, but it would be nice to have included
whenever version 0.4 comes out
I have been working on a paper for apecs, it can be found here.
This is still a prepublication version, but it's nearing completion and deprecates the previous tutorials, so it will serve as a manual for now.
If you have any criticism/suggestions/questions, please use this issue to report them!
I'd like to be able to use generalBracket inside SystemT.
cfold
is strict in its accumulator, which means you cannot short-circuit. A lazy cfoldr
and cfoldMap
(based on unboxed vector equivalents) would allow for this. The Any
and All
are practical examples of this laziness's usefulness.
While trying to build Stackage Nightly I ran into:
Building library for apecs-gloss-0.2.2..
[2 of 2] Compiling Apecs.Physics.Gloss
/var/stackage/work/unpack-dir/unpacked/apecs-gloss-0.2.2-0d17c2d82e07e1a43f9146bf720e1484542160d124d4ce3ccf59e13ca31d0697/src/Apecs/Physics/Gloss.hs:37:100: error:
• Could not deduce (Has w IO Physics)
arising from a use of ‘foldfn’
from the context: Has w IO Shape
bound by the type signature for:
drawBody :: forall w.
Has w IO Shape =>
(Body, Transform, ShapeList) -> System w Picture
at src/Apecs/Physics/Gloss.hs:36:1-78
• In the first argument of ‘foldM’, namely ‘foldfn’
In the second argument of ‘(<$>)’, namely
‘foldM foldfn mempty shapes’
In the expression:
color shColor . worldTransform transform
<$> foldM foldfn mempty shapes
|
37 | drawBody (btype, transform, ShapeList shapes) = color shColor . worldTransform transform <$> foldM foldfn mempty shapes
| ^^^^^^
Hello, I ran into this library after browsing random projects.
I noticed this part:
Fast - Performance is competitive with Rust ECS libraries (see benchmark results below)
However, I cannot find the benchmark results compared to an actual Rust ECS library. Where is it? Without it, I (and likely many others) cannot help doubting the "competitive performance" part.
windowToWorld :: Camera -> (Float,Float) -> V2 Float
windowToWorld (Camera cx cs) (x,y) = V2 x y ^/ cs - cx
is should be :
windowToWorld :: Camera -> (Float,Float) -> V2 Float
windowToWorld (Camera cx cs) (x,y) = V2 x y ^/ cs + cx
It'd be nice to be able to target entities that don't have a particular component.
For example, I'd like to be able to rmap both of the following functions simultaneously, and get the correct behavior:
defaultUpdatePos :: (Position, Velocity) -> Position
updatePosButHitWalls :: (Position, Velocity, Collision) -> (Position, Velocity)
proposed syntax for this might be something like:
defaultUpdatePos :: (Position, Velocity, Without Collision) -> Position
defaultUpdatePos (Position p, Velocity v, Without) = ...
First of all, thanks for all your work on this awesom library! 🍻
Does the library support concurrency? As I understand, it'd seem so, since it uses IORef
as the IO
storage. But the current nextEntity
seems to be susceptible to race conditions:
nextEntity :: (MonadIO m, Get w m EntityCounter) => SystemT w m Entity
nextEntity = do EntityCounter n <- get global
setReadOnly global (EntityCounter $ n+1)
return (Entity . getSum $ n)
If two or more threads concurrently call nextEntity
, there is a chance that two or more of them will get the same Entity
value.
Is my understanding correct? Or is there something that prevents this situation from occuring?
Cheers!
I cloned the apecs project and tried to build it with stack, so that I can try the shmup example.
However, stack build came up with the following error.
Is this an actual issue or am I doing something wrong?
Thanks!
C:\apecs>stack build
Building all executables for `examples' once. After a successful build of all of them, only specified executables will be rebuilt.
examples> build (exe)
examples> Preprocessing executable 'constraints' for examples-0.1.0.0..
examples> Building executable 'constraints' for examples-0.1.0.0..
examples> [1 of 1] Compiling Main
examples> ghc.exe: unable to load package `apecs-physics-0.4.5'
examples> ghc.exe: | C:\apecs\.stack-work\install\daa122fe\lib\x86_64-windows-ghc-8.6.5\apecs-physics-0.4.5-8viuxTPOf3sKmCxD9w6vQN\HSapecs-physics-0.4.5-8viuxTPOf3sKmCxD9w6vQN.o: unknown symbol `pthread_cond_wait'
-- While building package examples-0.1.0.0 (scroll up to its section to see the error) using:
C:\sr\setup-exe-cache\x86_64-windows\Cabal-simple_Z6RU0evB_2.4.0.1_ghc-8.6.5.exe --builddir=.stack-work\dist\e626a42b build exe:constraints exe:helloworld exe:shmup exe:simple exe:tumbler --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I'll work on a minimal example later, but for now
type AllComponents = ((AttachedTo, Selected, Symbol, Circle, Rotation, Charge), (Position, Velocity, Mass, Bullet, Player, Gas, Asteroid))
destroyEntity :: (MonadIO m, Has w m Position, Has w m AllComponents) => Entity -> SystemT w m ()
destroyEntity e = do
destroy e (Proxy :: Proxy AllComponents)
-- destroy e (Proxy :: Proxy Position)
Using my destroyEntity
function does not remove the Position
component (a count of them keeps increasing), but if I un-comment the explicit Position destroy it works as I expect.
https://hackage.haskell.org/package/judy
This looks beneficial for those components that fit into 64 bits (on 64-bit platform). Think IntMap
sans the IORef
redirection. All those Position Float Float
would snug right in.
Actually, with some bit twiddling multiple power-of-2 values can be compressed into a single cell. Or spread over multiple cells (to be benchmarked).
As far as I can tell, there are cases when a cfold
over a Cache k (Map SomeComponent)
will sometimes call the combining function on the same entity multiple times. If this is intended, it's surprising and should perhaps be documented.
Right now, when I try to run a system without adding anything to it, I get:
Prelude.foldl1: Empty List
This should be replaced with a more helpful message.
The monoid constraint for global component seems to only be used for mempty. No usage of other monoid functions is mentioned in the documentation. It is bothersome to write semigroup (<>) instances when they are not used. A custom typeclass or something like this https://hackage.haskell.org/package/data-default which declares only a mempty equivalent would be nice.
src/Apecs/THTuples.hs:94:30: error:
• Couldn't match type ‘Pat’ with ‘Type’
Expected: Name -> Type
Actual: Name -> Pat
• In the first argument of ‘(<$>)’, namely ‘VarP’
In the second argument of ‘ConP’, namely ‘(VarP <$> sNs)’
In the expression: ConP tupleName (VarP <$> sNs)
|
94 | sPat = ConP tupleName (VarP <$> sNs)
| ^^^^
src/Apecs/THTuples.hs:100:30: error:
• Couldn't match type ‘Pat’ with ‘Type’
Expected: Name -> Type
Actual: Name -> Pat
• In the first argument of ‘(<$>)’, namely ‘VarP’
In the second argument of ‘ConP’, namely ‘(VarP <$> wNs)’
In the expression: ConP tupleName (VarP <$> wNs)
|
100 | wPat = ConP tupleName (VarP <$> wNs)
| ^^^^
src/Apecs/THTuples.hs:133:30: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘ConP’ is applied to too few arguments
In the first argument of ‘Match’, namely ‘(ConP 'False [])’
In the expression:
Match
(ConP 'False []) (NormalB $ AppE (VarE 'return) (ConE 'False)) []
In the first argument of ‘LamCaseE’, namely
‘[Match
(ConP 'False []) (NormalB $ AppE (VarE 'return) (ConE 'False)) [],
Match (ConP 'True []) (NormalB vb) []]’
|
133 | (LamCaseE [ Match (ConP 'False []) (NormalB$ AppE (VarE 'return) (ConE 'False)) []
| ^^^^^^^^^^^^^^
src/Apecs/THTuples.hs:134:30: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘ConP’ is applied to too few arguments
In the first argument of ‘Match’, namely ‘(ConP 'True [])’
In the expression: Match (ConP 'True []) (NormalB vb) []
In the first argument of ‘LamCaseE’, namely
‘[Match
(ConP 'False []) (NormalB $ AppE (VarE 'return) (ConE 'False)) [],
Match (ConP 'True []) (NormalB vb) []]’
|
134 | , Match (ConP 'True []) (NormalB vb) []
| ^^^^^^^^^^^^^
src/Apecs/THTuples.hs:140:34: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘sPat’ is applied to too few arguments
In the expression: sPat
In the first argument of ‘Clause’, namely ‘[sPat, etyPat]’
In the expression:
Clause
[sPat, etyPat] (NormalB $ liftAll tuplE (explGetF <$> sEs)) []
|
140 | [ FunD explGetN [Clause [sPat, etyPat]
| ^^^^
src/Apecs/THTuples.hs:144:37: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘sPat’ is applied to too few arguments
In the expression: sPat
In the first argument of ‘Clause’, namely ‘[sPat, etyPat]’
In the expression:
Clause
[sPat, etyPat]
(NormalB
$ foldr
explExistsAnd (AppE (VarE 'pure) (ConE 'True))
((`AppE` etyE) . explExistsF <$> sEs))
[]
|
144 | , FunD explExistsN [Clause [sPat, etyPat]
| ^^^^
src/Apecs/THTuples.hs:150:34: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘sPat’ is applied to too few arguments
In the expression: sPat
In the first argument of ‘Clause’, namely ‘[sPat, etyPat, wPat]’
In the expression:
Clause
[sPat, etyPat, wPat]
(NormalB $ sequenceAll (zipWith explSetF sEs wEs)) []
|
150 | [ FunD explSetN [Clause [sPat, etyPat, wPat]
| ^^^^
src/Apecs/THTuples.hs:150:48: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘wPat’ is applied to too few arguments
In the expression: wPat
In the first argument of ‘Clause’, namely ‘[sPat, etyPat, wPat]’
In the expression:
Clause
[sPat, etyPat, wPat]
(NormalB $ sequenceAll (zipWith explSetF sEs wEs)) []
|
150 | [ FunD explSetN [Clause [sPat, etyPat, wPat]
| ^^^^
src/Apecs/THTuples.hs:156:38: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘sPat’ is applied to too few arguments
In the expression: sPat
In the first argument of ‘Clause’, namely ‘[sPat, etyPat]’
In the expression:
Clause
[sPat, etyPat] (NormalB $ sequenceAll (explDestroyF <$> sEs)) []
|
156 | [ FunD explDestroyN [Clause [sPat, etyPat]
| ^^^^
src/Apecs/THTuples.hs:162:38: error:
• Couldn't match expected type ‘Pat’
with actual type ‘[Pat] -> Pat’
• Probable cause: ‘sPat’ is applied to too few arguments
In the expression: sPat
In the first argument of ‘Clause’, namely ‘[sPat]’
In the expression:
Clause
[sPat]
(NormalB
$ foldl
explMembersFold (explMembersF (head sEs))
(explExistsF <$> tail sEs))
[]
|
162 | [ FunD explMembersN [Clause [sPat]
| ^^^^
cabal: Failed to build apecs-0.9.3.
I got the same errors with v0.8.3 and up.
As a Hackage trustee, I have created some Hackage revisions that should prevent users from getting these errors. See e.g. https://hackage.haskell.org/package/apecs-0.8.3/revisions/.
Hey, thanks again for your incredible library :)
After seeing people being mislead by my 4-year-old blog post both on discord and in issues like #87 , I thought I'd do another quick project and experiment with how things are done now. Sorry for self-advertising.
https://aas.sh/blog/notakto-a-haskell-game-with-apecs-and-raylib/
I don't know if you think this serves your post as much as my previous one does, but feel free to include it on your README as you see fit, maybe replacing my previous post or marking the old one as depreciated?
This post comes with a project as well: Notakto. I'll let you decide what you want to do with it.
Thank you again for your library, and my next project also uses apecs so expect more soon :)
I can't seem to find any info about why getAll was removed except the CHANGELOG.md, which states "Removed: getAll and count, which were made redundant by cfold.", which is simply untrue. "members", which was mentioned here (https://github.com/jonascarpay/apecs/blob/master/examples/Shmup.md) and seems to serve the same function, but does not actually exist. How, in "modern" apecs, would I go about accessing the Player's position (like here: https://aas.sh/blog/making-a-game-with-haskell-and-apecs/) monadically?
At the moment, the only thing we have access to in a collision handler is the normal vector and the colliding bodies (not shapes). There's more information available from the cpArbiter
, though, notably the colliding shapes, along with the actual points that are colliding, the collision impulse, the kinetic energy lost to friction, and so on. The shape information is really useful in order to, for example, use Sensor
s to detect contact.
Hi, I was playing with apecs in GHCJS backend and found that some high level features are behaving wrong.
Here the repo with minimal Nix environment to reproduce the bug: https://github.com/NCrashed/apecs-ghcjs-tests
If I use Cace
storage, I get the following runtime exception:
Cache miss!
CallStack (from HasCallStack):
error, called at src/Apecs/Stores.hs:132:13 in apecs-0.7.2-CGfWKAFxnsuIyWdarfLW0G:Apecs.Stores
The code of apecs related part:
import Apecs
import Control.Monad
import Control.Monad.IO.Class
data ComponentA = ComponentA
deriving (Show)
instance Component ComponentA where
type Storage ComponentA = Map ComponentA
data ComponentB = ComponentB
deriving (Show)
instance Component ComponentB where
type Storage ComponentB = Cache 100 (Map ComponentB)
data ComponentC = ComponentC
deriving (Show)
instance Component ComponentC where
type Storage ComponentC = Cache 100 (Map ComponentC)
data World = World {
worldAs :: !(Storage ComponentA)
, worldBs :: !(Storage ComponentB)
, worldCs :: !(Storage ComponentC)
, worldEs :: !(Storage EntityCounter)
}
instance Monad m => Has World m ComponentA where getStore = asks worldAs
instance Monad m => Has World m ComponentB where getStore = asks worldBs
instance Monad m => Has World m ComponentC where getStore = asks worldCs
instance Monad m => Has World m EntityCounter where getStore = asks worldEs
newWorld :: MonadIO m => m World
newWorld = do
worldAs <- explInit
worldBs <- explInit
worldCs <- explInit
worldEs <- explInit
pure World{..}
issue01 :: MonadIO m => m ()
issue01 = do
w <- newWorld
runWith w $ do
_ <- replicateM 10 $ void $ newEntity (ComponentA, ComponentB)
cmapM_ $ \(a :: ComponentA, b :: ComponentB, e :: Entity) -> liftIO $ print e
modify :: forall w m c. (Get w m c, Set w m c) => Entity -> (c -> c) -> SystemT w m ()
-- into
modify :: forall w m x y. (Get w m x, Set w m y) => Entity -> (x -> y) -> SystemT w m ()
Some things about constraints are iffy:
WVec
. For example, I create a pivot joint with PivotJoint
at some position, but I want to draw the pivot point on a body which moves around. The WVec
isn't updated, so it isn't possible to do so. Chipmunk converts it to the PivotJoint2
representation and provides functions to grab it. These could be implemented like MaxForce
, but really are only accessible in pivot joints.Perhaps the ConstraintType
should be lifted into the type of Constraint
somehow?
rust is my main language and im just curios how you have implemented this, is is parralel, can you have custom dispatchers etc
The readme claims you can put the shape in the same constructor as a dynamic body:
let ballShape = cCircle 0.5
newEntity ( DynamicBody
, Shape ballShape
, Position (V2 0 3)
, Density 1
, Elasticity 0.9
, BodyPicture . color red . toPicture $ ballShape )
When in fact the type of the Shape constructor is
Shape :: Entity -> Convex -> Shape
Requiring you to do
let ballShape = cCircle 0.5
e <- newEntity ( DynamicBody
, Position (V2 0 3)
, Density 1
, Elasticity 0.9
, BodyPicture . color red . toPicture $ ballShape )
newEntity $ Shape e ballShape
Or
let ballShape = cCircle 0.5
e <- newEntity ( DynamicBody
, Position (V2 0 3)
, Density 1
, Elasticity 0.9
, BodyPicture . color red . toPicture $ ballShape )
set newEntity (Shape e ballShape)
I'm not sure that second one works either.
apecs > [ 5 of 12] Compiling Apecs.THTuples
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:58:11: error:
apecs > • Couldn't match expected type ‘(Type -> TySynEqn) -> Dec’
apecs > with actual type ‘Dec’
apecs > • The first argument of ($) takes one argument,
apecs > but its type ‘Dec’ has none
apecs > In the expression:
apecs > TySynInstD strgN
apecs > $ TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)
apecs > In the fourth argument of ‘InstanceD’, namely
apecs > ‘[TySynInstD strgN
apecs > $ TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)]’
apecs > |
apecs > 58 | [ TySynInstD strgN $
apecs > | ^^^^^^^^^^^^^^^^^^...
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:58:22: error:
apecs > • Couldn't match expected type ‘TySynEqn’ with actual type ‘Name’
apecs > • In the first argument of ‘TySynInstD’, namely ‘strgN’
apecs > In the expression: TySynInstD strgN
apecs > In the expression:
apecs > TySynInstD strgN
apecs > $ TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)
apecs > |
apecs > 58 | [ TySynInstD strgN $
apecs > | ^^^^^
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:59:20: error:
apecs > • Couldn't match expected type ‘Maybe [TyVarBndr]’
apecs > with actual type ‘[Type]’
apecs > • In the first argument of ‘TySynEqn’, namely ‘[varTuple]’
apecs > In the second argument of ‘($)’, namely
apecs > ‘TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)’
apecs > In the expression:
apecs > TySynInstD strgN
apecs > $ TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)
apecs > |
apecs > 59 | TySynEqn [varTuple] (tupleUpT . fmap strgT $ vars)
apecs > | ^^^^^^^^^^
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:82:15: error:
apecs > • Couldn't match expected type ‘(Type -> TySynEqn) -> t’
apecs > with actual type ‘Dec’
apecs > • The first argument of ($) takes one argument,
apecs > but its type ‘Dec’ has none
apecs > In the expression:
apecs > TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > In an equation for ‘elemI’:
apecs > elemI
apecs > = TySynInstD elemN
apecs > $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > • Relevant bindings include
apecs > elemI :: t (bound at src/Apecs/THTuples.hs:82:7)
apecs > |
apecs > 82 | elemI = TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:82:26: error:
apecs > • Couldn't match expected type ‘TySynEqn’ with actual type ‘Name’
apecs > • In the first argument of ‘TySynInstD’, namely ‘elemN’
apecs > In the expression: TySynInstD elemN
apecs > In the expression:
apecs > TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > |
apecs > 82 | elemI = TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > | ^^^^^
apecs >
apecs > /tmp/stack7315/apecs-0.8.2/src/Apecs/THTuples.hs:82:43: error:
apecs > • Couldn't match expected type ‘Maybe [TyVarBndr]’
apecs > with actual type ‘[Type]’
apecs > • In the first argument of ‘TySynEqn’, namely ‘[varTuple]’
apecs > In the second argument of ‘($)’, namely
apecs > ‘TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)’
apecs > In the expression:
apecs > TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > |
apecs > 82 | elemI = TySynInstD elemN $ TySynEqn [varTuple] (tupleUpT $ fmap elemT vars)
apecs > | ^^^^^^^^^^
apecs >
-- While building package apecs-0.8.2 using:
/home/spacekitteh/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_3.0.0.0_ghc-8.8.1 --builddir=.stack-work/dist/x86_64-linux/Cabal-3.0.0.0 build --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1```
I have a component Deleted
, mostly not tagged on any entity. If I cmap $ \(Not :: Not Deleted, ...) -> ...
, none of the entities get updated, even if Deleted
is not on any of them.
For some reason the definition of Unique
's explMembers
has a significant effect on the benchmark results. This is weird, because the benchmark does not use Unique
at all.
If anyone has a clue as to why this could happen, please let me know.
These definitions are fast:
explMembers (Unique eref _) = U.singleton <$> readIORef eref -- old, unsafe dfn
explMembers (Unique eref _) = f <$> readIORef eref
where f (-1) = mempty
f x = U.singleton x
benchmarking ecs_bench/init: 283.5 μs (281.4 μs .. 285.9 μs)
benchmarking ecs_bench/step: 330.4 μs (328.2 μs .. 333.7 μs)
This one's slow:
explMembers (Unique eref _) = do
e <- readIORef eref
return $ if e == -1 then mempty else U.singleton e
benchmarking ecs_bench/init: 329.1 μs (323.0 μs .. 335.6 μs)
benchmarking ecs_bench/step: 372.0 μs (368.5 μs .. 375.7 μs)
I'm trying to compile apecs
as a dependncy of a project on Windows. I'm using stack
with the lts-13.0
resolver, which provides GHC 8.6.3. My stack.yaml
has the github repository as one of its extra-deps
. More specifically, it has:
extra-deps:
- github: jonascarpay/apecs
commit: "5b0585b6b435f156f638440facfdb8c02a64493d"
subdirs: ["apecs", "apecs-stm"]
Running stack build
and leaving my computer for over an hour shows it still trying to compile apecs
. Weirdly enough it works fine on Linux and macOS, which sort of implies that I should be making a GHC issue as opposed to one in this library. Still, I figured you'd like to know about it!
I think it would be very useful to be able to create read-only snapshots of the World state. For example when running different threads for graphics and game logic, the game logic could put snapshots of the World into an MVar to give the graphics thread access to a consistent state of the world whenever it starts a new frame. There would be no need to stop the game logic while talking to the graphics API.
Snapshots could also be useful for savegames, replays and other things. Maybe even a subset of the components could be specified when snapshotting to avoid retaining unwanted data.
I've noticed that most of the Stores are implemented as an IORef with a pure value inside, so I suppose the snapshots could mostly be made with neglegible computational overhead.
free(): invalid next size (fast)
Double free error in the constraints example. I haven't been able to consistently reproduce the issue, but it seems to be related to constraints and have appeared in 0.3.
Currently, apecs uses the mempty
member of Monoid
to provide a default for global values. This makes sense initially: it's the standard Haskell way to represent the "default" value of something.
However, problems come when we start defining other global components. I often have global components which do not sensibly fit the Semigroup
or Monoid
laws. For example, given something like this:
newtype Score = Score { getScore :: Int }
There's really no sensible way for me to "monoidally combine" scores, because they're not intended to have that kind of structure. I could always leave the instances blank except for mempty
, or write rule-breaking instances, but that might be surprising to other users.
As such, I propose a new typeclass:
class ApecsGlobal a where
apecsDefault :: a
We could then use apecsDefault
instead of mempty
where needed. We could even define this:
instance (Monoid a) => ApecsGlobal a where
apecsDefault = mempty
This would provide backwards-compatibility.
The stores in apecs currently run in MonadIO
. I think it might be worth moving to PrimMonad
from primitive, it allows apecs to be used in ST.
Linux builds tend to halt indefinitely when using System.Exit. Not always, but quite often. Is there a canonical way to do a clean shutdown?
Issue #49 still reproduces for me. I haven't been able to get around the issue, but what I can say is that even though CollisionType
appears to be properly exported as of today, CollisionGroup
is not, and that appears implicated in the code.
I really want to merge the SystemT branch, there are a lot of neat things it allows for. Unfortunately, there is a performance hit that needs to be addressed first.
Changes:
System w a = SystemT w IO a
, and SystemT w m a = ReaderT w m a
Store s
is now Store m s
, with e.g. explGet s -> Int -> Stores s -> m ()
. Store
instances have been changed to instances of Store IO
.Has
and Log
have a similar new m
argument.The actual, non-type signature code is mostly intact, which suggests that the cause is type-related.
There are two similar functions:
cmapM :: forall w m cx cy. (Get w m cx, Set w m cy, Members w m cx) => (cx -> SystemT w m cy) -> SystemT w m ()
cmapM_ :: forall w m c a. (Get w m c, Members w m c) => (c -> SystemT w m a) -> SystemT w m ()
I can see the reasons for why cmapM_
has this type - to match mapM_
- but if you intended to use cmapM
but wrote cmapM_
accidentally, there is nothing to warn you that your properties won't get set:
cmapM_ $ \(Velocity v) -> do
liftIO $ print v
pure $ Velocity (v - 1)
This compiles fine. I propose constraining it to
cmapM_ :: forall w m c. (Get w m c, Members w m c) => (c -> SystemT w m ()) -> SystemT w m ()
so now it's impossible to make mistakes like above. I think this is a valid change: cmapM
already deviates from mapM
by returning ()
, so maintaining a close match to those types doesn't seem so important.
Do you know what distinguishes entity-component-systems from rule-based systems? I'm curious because I've been trying to layer a rules engine in Haskell ontop of reflex
that might be better off using something like apecs
.
My inspiration came from a declarative programming framework called precept that's based on logic-programming and rule-based systems.
In precept
you have facts and rules, which correspond almost exactly to components and systems. Entities are then just aggregations of facts with the same id. New facts are added by user inputs, and rules are fired if their conditions are met, the consequences of which can be adding or removing facts. The conditions and consequences being the left hand side and right hand side of a lambda passed to cmap
.
I'm sure there's an important distinction between these frameworks, even if only in perspective, but I haven't spent too much time with either to really say. Abstractly, they both seem to be based on defining application logic in terms of rules, and modeling data as points rather than products.
@alex-dixon might also have some input on this.
As can be seen at
https://matrix.hackage.haskell.org/package/apecs@1548960197
every release of apecs
was affected by having incorrect version bounds.
In my function as a Hackage Trustee I went ahead and tried to reverse engineer some of the omitted bounds and performed respective Hackage metadata revisions:
The result of this upon the build-plans can be reviewed at
https://matrix.hackage.haskell.org/package/apecs@1549227956
This has started to propagate to other packages (e.g. apecs-stm
) which are reverse dependencies and resulting in breaking build-plans for those packages until it was rectified via the metadata revision mentioned above.
We need your help! Please help us ensure a good user experience for users of Hackage/Cabal by the use of the PVP mandated upper bounds in order to reduce the penalty on the Hackage infrastructure as well as to avoid unnecessary extra work for you as well as for us Hackage Trustees!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.