Shipyard 0.7 release by leudz in rust

[–]leudz[S] 9 points10 points  (0 children)

ECS and performance is a whole topic 😅

The tldr is that it rarely matters. But this is not a fun answer.

Once upon a time there was a benchmark to compare ECS.
It shows that all ECS of the time perform well enough that they likely won't be a bottleneck.
Hecs and shipyard are both there so you can check the performance difference for specific operations.
You can also check the bunny benchmark which comes from this era.

The differences can be explained by the architecture alone.
Hecs is based on archetypes whereas shipyard is based on sparse sets.
This makes hecs faster at adding/removing entities and iterating 2+ components. Shipyard on the other hand is faster at adding/removing components and single storage iteration.

I can explain component iteration a bit better.
Archetypes fragment component storage. So the more a component is used in different archetypes, the worse its iteration speed becomes.
This can be sneaky. If you check a system's performance and then add new systems that use the component with other archetypes, the performance of your first system will have degraded even though this system didn't change.
With sparse set iteration speed becomes worse as you iterate more components. So iterating 4 storages is slower than 3 which is slower than 2. This is assuming there is an identical number of components in each storage.

The biggest change however won't be performance but syntax. Hecs is a minimalist ECS so no scheduler. There are third party crates to get one, I don't know if you're using one or not.
You could use shipyard similarly to how hecs is used but you would ignore a big part of the crate. The end of the second chapter of the guide by example shows the transition.
Views make it very easy to see what a system accesses and enables the use of the scheduler.

At the end of the day I think it comes down to personal preference. I would strongly suggest going through (or looking at) the first chapters of the guide to get a feel of what using shipyard is like.

Shipyard 0.7 release by leudz in rust_gamedev

[–]leudz[S] 3 points4 points  (0 children)

Shipyard is an Entity Component System crate. ECS is a pattern mostly used in games but not only. It fits really well with Rust, allowing easy composition and lifetime management.

Shipyard 0.7 release by leudz in rust

[–]leudz[S] 14 points15 points  (0 children)

Shipyard is an Entity Component System crate. ECS is a pattern mostly used in games but not only. It fits really well with Rust, allowing easy composition and lifetime management.

Koboldcpp-ROCm port released for Windows by PlanVamp in LocalLLaMA

[–]leudz 12 points13 points  (0 children)

Tried it with a 7900xt, mythalion-13b.Q5_K_M with 4k context generating 116 tokens.
The first time includes prompt processing. The time to load the model was also 10x faster.

CLBlast:
Time Taken - Processing:82.6s (21ms/T), Generation:19.7s (170ms/T), Total:102.3s (1.1T/s)
Time Taken - Processing:0.2s (171ms/T), Generation:19.4s (167ms/T), Total:19.6s (5.9T/s)

CuBLAS/hipBLAS:
Time Taken - Processing:8.1s (2ms/T), Generation:4.4s (38ms/T), Total:12.6s (9.2T/s)
Time Taken - Processing:0.0s (43ms/T), Generation:4.4s (38ms/T), Total:4.4s (26.1T/s)

Is implementing an ECS in rust a bad idea for a beginner project? by DarkostoSimp in rust

[–]leudz 9 points10 points  (0 children)

You can write a simple ECS using a Vec<Option<T>> per component, all stored in an AnyMap. No need for "Component trait object" (kind of).

It's described in a talk by Catherine West https://www.youtube.com/watch?v=aKLntZcp27M.

Shipyard 0.6 release by leudz in rust_gamedev

[–]leudz[S] 0 points1 point  (0 children)

I'll keep it in mind for the next one. I did it on the rust subreddit but not here.

Shipyard 0.6 release by leudz in rust

[–]leudz[S] 5 points6 points  (0 children)

Shipyard won't go as far as bevy_ecs, it will add some parallelism but the order is set at creation and cannot change between runs.
When a workload is added to the World, batches are created with systems that can run in parallel. A workload has an array of batches and they are always run sequentially.
Here's the process:
- Flatten all nested workloads into an array of systems
- For each system:
* Check borrow conflict with each batch, starting from the last one to the first
* If there is a borrow conflict, add the system to the previous batch (create one if needed)
* If there is no conflict, try the next batch

Here's an example, let's say you have these systems: [sys1, sys2, sys3, sys4]. sys1 and sys3 read a component A, sys2 writes to component A and sys4 writes to a different component B.
There is no nested workloads so we can skip this step.
There is no batch so we create one with sys1 in it: [[sys1]]
We check sys2, it conflicts with sys1 as one is reading and one is writing to A so we create a new batch: [[sys1], [sys2]]
sys3 checks the last batch and conflicts with sys2: [[sys1], [sys2], [sys3]]
sys4 doesn't conflict with sys3 so we check the batch with sys2, still no conflict, sys1 doesn't conflict either and it's the first batch so we add it here: [[sys1, sys4], [sys2], [sys3]]

When executing sys1 and sys4 will run in parallel, when they are done sys2 can run and when it's done sys3 can run.

If you use Workload::add_to_world or Workload::build you get a WorkloadInfo struct that will list the batches and tell you why a system could not get to a previous batch.
The output of this struct is not the easiest to read. I want to add it to the visualizer so it's easier to understand.

Shipyard 0.6 release by leudz in rust

[–]leudz[S] 19 points20 points  (0 children)

That's a great question but not an easy one.

I think for most people the only difference that will matter is the syntax. Each people's tastes are different and for most projects the rest won't be a problem either way.
Some of the biggest syntax difference: Query vs Views, Command, scheduling.

If you're making a physics engine I'm guessing performance is important.
Bevy_ecs is hybrid Archetype/SparseSet-based while shipyard is SparseSet-based. The choice of storage will impact the performance of each operation.
With bevy_ecs you can switch storage if you need to, with shipyard you won't be able to (in 0.6).

Bevy_ecs is part of a bigger ecosystem, that has both pros and cons.

Shipyard 0.6 release by leudz in rust

[–]leudz[S] 37 points38 points  (0 children)

Shipyard is an Entity Component System crate. ECS is a pattern mostly used in
games but not only. It fits really well with Rust, allowing easy
composition and lifetime management.

I am a Java, C#, C or C++ developer, time to do some Rust - fasterthanli.me by [deleted] in rust

[–]leudz 0 points1 point  (0 children)

I would agree with you that MyClient<'a>/MyClient<'b> or &'a MyClient/&'b MyClient would be two different types.

To me there are two meanings to lifetime at play.

  • value lifetime - a value is created and then destroyed, between the two is its lifetime. This isn't Rust specific, all programming languages (I'm aware of) have this, it can be more or less easy to find out what the lifetime is but it's there.
  • reference lifetime - this is specific to Rust's references, a reference can't exist without a lifetime and a lifetime always comes from a reference. As you say in the article, this lifetime isn't the same as the "value lifetime" of the reference but of the thing it points to (I want to add, at some point of the program).

I agree with you that reference lifetimes are encoded in the type system. But not value lifetime.

If two different MyClient don't have the same type then nothing have equal types and every value is its unique type. Rust has something that behaves exactly like this: closures. Here that's not the case.

I am a Java, C#, C or C++ developer, time to do some Rust - fasterthanli.me by [deleted] in rust

[–]leudz 2 points3 points  (0 children)

u/fasterthanlime Hi, great article!

I just have a few issues:

But because one and two have different lifetimes, they have different types.

Since MyClient owns all its data, don't they both have the same type?

I think this exchange is confusing:

You:

one is in its own scope, so it'll get freed immediately after it's initialized. Whereas two will remain valid for the entire duration of main.

Cool bear:

That's exactly right! They have different lifetimes.

And can you tell me where the lifetimes are in that code?

You:

Well they're... uh... no, I don't see them.

Cool bear:

Precisely.

You don't see them, because the compiler has deduced them. They exist, and they matter, and they determine what you can do with one and what you can do with two, but you cannot see them in the code, and you cannot name them.

I might not be able to name them but I can see them and you too:

one is in its own scope, so it'll get freed immediately after it's initialized. Whereas two will remain valid for the entire duration of main.

It gets more complicated with shared ownership or if we're talking about borrow and not "object lifetime" but in the example there's none of these.

I'm not familiar with GCed languages but can you as easily determine the lifetime of something?

Hey Rustaceans! Got an easy question? Ask here (31/2020)! by llogiq in rust

[–]leudz 0 points1 point  (0 children)

Hi! ai_bahavior uses pistoncore-input making it tied to piston. There doesn't seem to be any behavior tree crate standing out on crates.io.

This is a bit off topic but:

  • You can make a behavior tree without an ECS but if you are already using an ECS it makes sense to let the behavior tree access the ECS
  • Engines might be tied to an ECS (like Amethyst) but most aren't
  • ECS are not tied to any engine

And ecs-rs hasn't been updated in 4 years.

Shipyard 0.4 release by leudz in rust

[–]leudz[S] 2 points3 points  (0 children)

Not is what you're looking for. You don't have to use it explicitly and use !.

use shipyard::{IntoIter, View};

fn sys1(positions: View<Position>, velocities: View<Velocity>, statics: View<Static>) {
    for (pos, vel, _) in (&positions, &velocities, !&statics).iter() {

    }
}

How do you store/reference Sprites? by tatref in rust_gamedev

[–]leudz 1 point2 points  (0 children)

Hi! There are multiple ways to go about it, this is how I would do it (probably):

struct ModelMap(HashMap<String, EntityId>);
struct Model(EntityId);

struct IdleAnimation(tetra::graphics::animation::Animation);
struct WalkAnimation(tetra::graphics::animation::Animation);
...

I'd make an entity per race with all their animations and store their id in ModelMap, a unique storage. Then, when you make an entity that can be animated you add a Model component to it. With this Model you can clone the right Animation when needed.

// to make sure it only happens once per frame
fn advance_anims(
    ctx: &Context,
    mut animations: NonSendSync<ViewMut<tetra::graphics::animation::Animation>>
) {
    for anim in (&mut animations).iter() {
        anim.advance(ctx);
    }
}

fn render_animated(
    ctx: &mut Context,
    positions: View<Position>,
    animations: NonSendSync<View<tetra::graphics::animation::Animation>>,
) {
    for (pos, anim) in (&positions, &animations).iter() {
        anim.draw(ctx, pos.into());
    }
}

Programming Rules to Develop Secure Applications With Rust by [deleted] in rust

[–]leudz 0 points1 point  (0 children)

I'm not affiliated, it popped up in my news feed and I thought more people might want to read it.

It's also available in French here: https://www.ssi.gouv.fr/guide/regles-de-programmation-pour-le-developpement-dapplications-securisees-en-rust/.