Skip to content

Apecs 1.0#72

Draft
jonascarpay wants to merge 8 commits intomasterfrom
apecs1
Draft

Apecs 1.0#72
jonascarpay wants to merge 8 commits intomasterfrom
apecs1

Conversation

@jonascarpay
Copy link
Owner

@jonascarpay jonascarpay commented Jul 12, 2020

apecs 1.0

This is an (experiment for) the next version of apecs. At the moment this branch contains 75% of a rewrite of the core apecs package. The goal is to first get that right, and then port the rest of the apecs packages. See below for more information about what, how, and why. Help and feedback is welcome!

Todo

  • Stores
    • Cache
      • polymorphic in backing vector
        I want this so I can have Storable vectors for things I want to send to the GPU
    • Spatial Hashes
  • Module structure
    • Rename Focus
  • Benchmarks
    • Profiling comparison
    • Performance tweaks
  • Stack support
  • Redesign newEntity/EntityCounter?
    • Make newEntity more general?
    • Make EntityCounter less of a special case?
  • Documentation/blog posts
    • How to use
    • How to go fast
    • Internals
  • Feature parity
    • Stores
    • Systems
    • Some things might need to be redesigned
      • Reactivity?
    • Some things might no longer be needed
  • Clean up Nix
  • CI
  • Haddocks
  • Tests
  • port secondary libraries
    • apecs-gloss
    • apecs-physics
    • apecs-stm
  • Fix version bounds
  • Everything else
  • Open questions
    • Multi-component stores?
      • What else?
      • Custom type errors?
        • No Generic or more specific instance
        • Components undefined for World member
        • probably others
    • SystemT: Synonym or newtype?

More information

This is the next version of apecs. The reason I'm (tentatively) calling it 1.0 is that it is so different from 0.9 that calling it 0.10 doesn't really do it justice. Also, I think apecs has been around long enough at this point to warrant a 1.0.

In short, the difference between this and 0.9 is that this version uses Generics where 0.9 used Template Haskell. The surface level of the library mostly stays the same, but the implementation is very different, and quite a bit simpler.

The improvements are mostly inspired by generic-lens. In the past, I tried writing apecs using Generics a number of times, but I was never able to really make it work out in a way I was happy with. generic-lens showed me how to do some of the things I was struggling with, and that it can be just as fast as handwritten/TH code. It took a while to get to actually get apecs to a point where it was an improvement over TH, but I think that time has come, and it might be a good idea to get some help and feedback.

Changes

Example.hs contains a rewrite of the example in the apecs readme:

newtype Position = Position Float deriving (Eq, Show)

newtype Velocity = Velocity Float deriving (Eq, Show)

data World
  = World
      (Map Position)
      (Map Velocity)
      EntityCounter
  deriving (Generic, Initialize IO)

main :: IO ()
main = do
  (w :: World) <- initialize
  flip runReaderT w $ do
    newEntity (Position 0, Velocity 3)
    newEntity (Position 1, Velocity 4)
    newEntity (Position 2, Velocity 5)
    cmap $ \(Position p, Velocity v) -> Position (p + v)
    cmapM_ $ \(Position p) -> liftIO $ print p

Instead of providing component declarations and generating the world from them, you now write the world yourself. As you can see in main, the mechanics of using apecs mostly stay the same, but defining components and the world is a lot more transparent. This makes it so that you can manipulate the World directly if you want, for instance if you want to copy or freeze it.

At its core, apecs is about 4 type classes; Get, Set, Destroy, and Members. This separates the concerns of the front-end (providing a safe, high-level language) the backend (fast memory access).
Previously, there were a number of extra classes that were necessary to make this actually work out though, like Has, and the Expl... family. Apecs 1.0 manages to do away with the noise however, and the core module truly only consists of the 4 core type classes. All the heavy lifting is done by the instances.

The most "magical" of these instances are those that allow a Generic world to inherit the instances of its constituent stores. This is the part that was copied from/inspired by generic-lens.
There are open questions about this part of the design space. For example, there is first-class support for stores that contain multiple components. This is nice for apecs-physics and reactive stores, but it makes for particularly bad error messages. We could write custom type errors, but we'll have to talk about whether it's worth the complexity.

Performance

The performance numbers are similar to 0.9. It appears that some things are slightly faster, and some things are slightly slower. Profiling or more benchmarks would be nice, and there might be more gains to be made, but it's good to know we're still fast.

@jonascarpay jonascarpay changed the title Initial commit in apecs repo Apecs 1.0 Jul 12, 2020
@jonascarpay jonascarpay mentioned this pull request Jul 23, 2020
@DavidEichmann
Copy link

Hey @jonascarpay! Hope you're well. May I ask what the status of Apecs 1.0 is? I'm considering contributing, but doing so in the middle of a significant rewrite sounds like a bad idea. The main thing I need is to remove IO from the API. I could e.g. attempt a refactoring on master to remove IO, but would such a change simply thrown out for Apecs 1.0?

@jonascarpay
Copy link
Owner Author

@DavidEichmann The core apecs package is pretty much at feature parity, but I have gotten completely lost in other projects. I might take some time over the weekend to make a more concrete plan for this branch.

As for what you're thinking of, it might be a good idea to discuss it in a separate issue/PR.
The parts that you seem to want to change are very close to identical between the branches, so I suspect that if you make a PR I can incorporate it directly here.

@sheyll
Copy link

sheyll commented Jun 6, 2021

I just want to let you know, that I am looking forward to apecs 1.0.

The rewritten Example just looks excellent. Getting the "world" from apecs` template Haskell somehow felt wrong.

@jonascarpay
Copy link
Owner Author

I'm happy to report that on c2ab52a I was able to change SystemT from a ReaderT into a StateT without any performance impact. That means first-class support for pure systems should work out nicely now, without having to go through ST.

Unfortunately I'm still not comfortable giving an actual ETA for this PR since I'm kind of distracted atm. I'm considering postponing porting apecs-physics, since that is by far the biggest task left.

@jonascarpay
Copy link
Owner Author

I did some performance investigations and it seems the difference with 0.9 is larger than I assumed. Some benchmarks perform ~20% better, but some perform 400% worse (the ecsBench benchmark, but running the step system 100 times). This is surprising to me, since generic lenses perform as well as TH lenses, and we're more or less doing the same thing here.
Since the recent StateT-based SystemT change further complicates things, for now I'm reverting this branch to the old ReaderT code. The StateT stuff will live on apecs1-statet.

The best case scenario is of course finding a way to make this branch perform on par with the 0.9 code, but if not I need to have a think whether this new API is worth the performance cost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants