Code Monkey home page Code Monkey logo

apecs's People

Contributors

3noch avatar anthonysuper avatar ashe avatar bodigrim avatar cocreature avatar dpwiz avatar ethercrow avatar gelisam avatar glocq avatar guibou avatar intolerable avatar jmromeroes avatar jonascarpay avatar jship avatar luke-clifton avatar mewhhaha avatar radicalautistt avatar simre1 avatar thalesmg avatar toku-sa-n avatar tomsmalley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apecs's Issues

cmapMIf and cmapM_If

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

Symbols as components?

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?

Contrary to documentation modify/$~ is unsafe

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 additionalMaybe` 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?

Community chat?

Is there a community Gitter, Discord, etc.? Would be cool to talk to people who are using this!

apecs-physics: Collision handlers confusion

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?

Maybe.fromJust: Nothing

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.

apecs-stm-0.1.4 fails to build with apecs-0.9.2

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.

fails to build with ghc-9.4/Cabal-3.8 for Stackage Nightly

--  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"                                                    
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^         

Missing test cases

  • Every entity exists iff it is contained in the list of members
  • The slice of (a,b) should consist of the same entities as a slice of (b,a), possibly in a different order.

Have travis build the examples

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.

error destroying component for entity seemingly guaranteed to have that component

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 name conflict

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

Paper

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!

lazy cfoldr and cfoldMap

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.

apecs-gloss-0.2.2 fails to build in Stackage Nightly

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
       |                                                                                                    ^^^^^^

Please enhance Benchmark Comparison chart

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.

Target entities without components

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) = ...

Question about `nextEntity` and concurrency

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!

Unable to load package `apecs-physics-0.4.5'

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

Cache store occasionally requires a double delete.

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.

Judy-backed storage in IO

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).

cfold over cache operates on same entity multiple times

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.

Build failure with GHC 9.2.1


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/.

New article for Apecs - maybe depreciate outdated one?

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 :)

Why is there no getAll / members?

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?

apecs-physics: Shapes in collision handlers

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 Sensors to detect contact.

GHCJS runtime errors

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

Allow different return type in modify

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 () 

apecs-physics: Constraints implementation

Some things about constraints are iffy:

  • MaxForce/MaxBias only apply to certain types of constraints, not all constraints
  • Getting the properties of constraints leads to immediately outdated data when the constraints take 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?

How similar to specs?

rust is my main language and im just curios how you have implemented this, is is parralel, can you have custom dispatchers etc

Physics documentation is inaccurate

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 doesn't build with template-haskell-2.15.0.0

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```

Performance mystery

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)

Compiler appears to infinite loop on windows

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!

Snapshotting, freezing or cloning the World

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.

C memory errors in apecs-physics

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.

The use of Monoid for Global values is a bit strange

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.

apecs-gloss hangs on exit

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?

Apecs-Physics collision handlers still nonfunctional

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.

SystemT performance

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.

Constraining the type of cmapM_

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.

Are systems just rules?

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.

Inaccurate version bounds in several `apecs` Hackage releases

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!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.