Resources
This (short) chapter will explain the concept of resources, data which is shared between systems.
First of all, when would you need resources? There's actually a great example in chapter 3, where we just faked the delta time when applying the velocity. Let's see how we can do this the right way.
#[derive(Default)]
struct DeltaTime(f32);
Note: In practice you may want to use
std::time::Durationinstead, because you shouldn't usef32s for durations in an actual game, because they're not precise enough.
Adding this resource to our world is pretty easy:
world.insert(DeltaTime(0.05)); // Let's use some start value
To update the delta time, just use
use specs::WorldExt;
let mut delta = world.write_resource::<DeltaTime>();
*delta = DeltaTime(0.04);
Accessing resources from a system
As you might have guessed, there's a type implementing system data
specifically for resources. It's called Read (or Write for
write access).
So we can now rewrite our system:
use specs::{Read, ReadStorage, System, WriteStorage};
struct UpdatePos;
impl<'a> System<'a> for UpdatePos {
type SystemData = (Read<'a, DeltaTime>,
ReadStorage<'a, Velocity>,
WriteStorage<'a, Position>);
fn run(&mut self, data: Self::SystemData) {
let (delta, vel, mut pos) = data;
// `Read` implements `Deref`, so it
// coerces to `&DeltaTime`.
let delta = delta.0;
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * delta;
pos.y += vel.y * delta;
}
}
}
Note that all resources that a system accesses must be registered with
world.insert(resource) before that system is run, or you will get a
panic. If the resource has a Default implementation, this step is usually
done during setup, but again we will come back to this in a later chapter.
For more information on SystemData, see the system data chapter.
Default for resources
As we have learned in previous chapters, to fetch a Resource in our
SystemData, we use Read or Write. However, there is one issue we
have not mentioned yet, and that is the fact that Read and Write require
Default to be implemented on the resource. This is because Specs will
automatically try to add a Default version of a resource to the World
during setup (we will come back to the setup stage in the next chapter).
But how do we handle the case when we can't implement Default for our resource?
There are actually three ways of doing this:
- Using a custom
SetupHandlerimplementation, you can provide this inSystemDatawithRead<'a, Resource, TheSetupHandlerType>. - By replacing
ReadandWritewithReadExpectandWriteExpect, which will cause the first dispatch of theSystemto panic unless the resource has been added manually toWorldfirst. - By using
Option<Read<'a, Resource>>, if the resource really is optional. Note that the order here is important, usingRead<'a, Option<Resource>>will not result in the same behavior (it will try to fetchOption<Resource>fromWorld, instead of doing an optional check ifResourceexists).
In the next chapter, you will learn about the different storages and when to use which one.