Skip to content

Releases: leudz/shipyard

Shipyard 0.7

12 Jul 07:54
Compare
Choose a tag to compare

This release should require very little to no changes to user code.

Guide

The guide continues to expand with a new Learn by example section.

Tracking new design

This is what took the most time, and in the end, the new design is syntactically identical to 0.6 for most users.

The new design allows for the easy-to-use derive of 0.6 while allowing downstream crate to specify additional tracking on a component they didn't define.
Let's take an example. You have 3 crates, crateA and crateB both depend on crateC.
crateA and crateB both use a component T that is defined in crateC. crateA cares about modification on T but crateB and crateC don't.
With 0.6 you would have to always track T modifications, in all three crates. The only place tracking could be defined was crateC.
With 0.7 you can omit tracking from crateB and crateC. And only track modifications in crateA.

The derive macro also allows any combination of tracking now where it only allowed a single kind of tracking in 0.6.

You can read more about tracking evolution in this post.

More convenience functions

Back in early versions all entity and component operations had to go through views.
With time I changed my mind on this and I added shortcuts to add entities or components directly from World.
0.7 adds more of these functions like World::iter.

#[derive(Component, Debug, PartialEq, Eq)]
struct U32(u32);
#[derive(Component, Debug, PartialEq, Eq)]
struct USIZE(usize);

let mut world = World::new();

let entity = world.add_entity((USIZE(0), U32(1)));

for (i, j) in &mut world.iter::<(&USIZE, &mut U32)>() {
    // <-- SNIP -->
}

Custom Views

Custom views are easier to create and more powerful than ever.

#[derive(Borrow, BorrowInfo, IntoIter)]
struct TransformView<'v> {
    pos: View<'v, Pos>,
    vel: View<'v, Vel>,
}

fn main() {
    // -- SNIP --

    world.run(|v_transform: TransformView| {
        for transform in v_transform.iter() {
            transform.pos;
            transform.vel;
        }
    });
}

Thanks @eldyer, @BoxyUwU and everyone that opened an issue or PR!

v0.5.0

29 Mar 04:38
Compare
Choose a tag to compare

This release took quite some time but I'm very happy of all the changes.
The api is now macro free. It took a few tries to get to this api for workloads but it works now. There is just a rustc weird behavior that makes it impossible to use values for workload systems. It's an acceptable price to get rid of the macro but it'd be great if it could be fixed.

Most notable changes:

  • Remove system! macro
    This macro was used to hide what information workloads need. For example using system!(check) expended to (|world: &World| world.try_run(check), check)).
    The scheduler would use the function on the right to define what the function on the left borrows. If the two functions didn't match you could run into runtime errors.

    The new api (.with_system(&check)) does not have this problem.

  • Remove packs
    Packs are removed temporarily, this implementation had too many limitations. The next implementation should not have the same problems but requires a fairly big amount of work so this version ships without packs and the next version should add them without modifying any existing api.

    remove was the most impacted, to remove two or more components you had to write Remove::<(usize, u32)>::remove((&mut usizes, &mut u32s), entity);.
    With 0.5 it's just (&mut usizes, &mut u32s).remove(entity).

  • Bulk add entity
    This new method is faster than adding entities one by one.

  • Remove Shiperator
    0.4 used a custom Iterator trait to modify some methods' behavior like filter.
    With 0.5, iterating an exclusive storage yields a Mut<T> by default which removes the need for a custom trait.

  • Accurate modification tracking by default
    In 0.4 when you tracked modification on a storage it would tag all components accessed exclusively regardless if they were modified or not.
    This is not true anymore, this code would tag exactly what you would expect:

    fn increment_even(mut u32s: ViewMut<u32>) {
        for mut i in (&mut u32s).iter() {
            if *i % 2 == 0 {
                *i += 1;
            }
        }
    }
  • No more try_*
    In 0.4 all fallible functions had two versions, one that panicked and one prefixed with try_ that returned a Result.
    Now all functions that can fail because of storage access return a Result while almost all others panic.

  • Workload building is more flexible
    In 0.4 you could only create workloads directly with World and it was borrowed for the entire building time.

    world
        .add_workload("Add & Check")
        .with_system(system!(add))
        .with_system(system!(check))
        .build();

    In 0.5 you only need to borrow World to actually add the workload to it.

    Workload::builder("Add & Check")
        .with_system(&add)
        .with_system(&check)
        .add_to_world(&world)
        .unwrap();

    You can also "prepare" systems in advance, merge WorkloadBuilders, extend a WorkloadBuilder with a system iterator or even nest multiple workloads.

  • Workload debug
    When you create a workload you can print information about how it will be scheduled and why two systems can't run at the same time.

  • Custom view
    A lot of internal traits, types and functions are exposed in 0.5. One of them is Borrow, it allows user defined views. Custom views are very important to build clean apis on top of Shipyard.
    For example this custom view (https://github.com/dakom/shipyard-scenegraph/blob/master/crate/src/views.rs#L10-L28) borrows 11 storages. Users won't have to borrow 11 views though, just a single one.
    Creating a custom view is currently fairly verbose, I want to add a proc macro that would automatically derive the necessary traits.

  • Custom storage
    Shipyard comes with two default storages: SparseSet and Unique. 0.5 allows you to define your own storages. You can then build a view for them and access them in systems for example.
    The advantage compared to simply storing your type in Unique is to be able to define what your storage should do when an entity is deleted.

v0.4.1

05 Jun 20:14
Compare
Choose a tag to compare

Fixes a bug reported by @hasenbanck where adding multiple workloads with the same name would cause a panic when running them.

  • New AddWorkload error
  • Simplify SetDefaultWorkload error

v0.4

25 Apr 22:37
Compare
Choose a tag to compare

What's new

  • Rework of systems and types used to borrow storage
  • Workloads had to be reworked to handle this change
  • Workloads can now return errors
  • Iterator and IntoIterator are now supported

For 0.3 users there's a migration guide to help with all the changes.

v0.3

29 Feb 22:41
Compare
Choose a tag to compare

What's new

  • User guide

    Learning to use a crate with the documentation can be hard. There's now a guide to explain what can be done with Shipyard and how.

  • No need to register components anymore

    Components had to be registered before accessing them by using World::new or World::register.
    Storages are now automatically created when they are first accessed.

  • !Send and !Sync components

    All components had to be Send + Sync, this is a strict limitation to be sure storages can use the parallel feature.
    With 0.3 !Send and !Sync types can be stored and accessed while still following Rust's rules. These rules limit threading for these types but doesn't always prevent it.

  • Unique components

    When we only need a single instance of a component, keeping an id around to access it can be annoying. A Unique component won't be attached to any entity but will have the storage all for itself.

  • Components sorting

  • No std support

  • And more 😉