pub struct Job<T> { /* private fields */ }Expand description
Work running on a worker thread, and its result once it’s done.
Start it with Job::spawn, then check Job::take each frame: the
game keeps running meanwhile. GPU work (making meshes, textures) belongs
on the game’s thread, after the job; building the data for it (heights,
MeshData, decoded images) belongs in the job. In a
browser, which has no threads here, the work runs when it’s spawned.
let mut job = Job::spawn(|| (1..=10).sum::<i32>());
// Each frame, until it's done:
let sum = loop {
if let Some(sum) = job.take() {
break sum;
}
};
assert_eq!(sum, 55);Implementations§
Source§impl<T: Send + 'static> Job<T>
impl<T: Send + 'static> Job<T>
Sourcepub fn spawn(work: impl FnOnce() -> T + Send + 'static) -> Self
pub fn spawn(work: impl FnOnce() -> T + Send + 'static) -> Self
Starts work on a worker thread (the shared pool, one thread per
core).
let heights = Job::spawn(|| vec![0.0f32; 129 * 129]);
assert_eq!(heights.wait().len(), 129 * 129);Examples found in repository?
examples/streaming.rs (line 129)
82fn main() {
83 let bench = std::env::var("BENCH").is_ok();
84 let shot = std::env::var("SHOT").ok();
85 let mut cells = CellStreamer::new(TILE, LOAD, KEEP);
86 let mut tiles: HashMap<(i32, i32), Tile> = HashMap::new();
87 let mut ground: Option<Material> = None;
88 let (mut yaw, mut speed, mut altitude) = (0.4f32, if bench { 600.0f32 } else { 120.0 }, 90.0f32);
89 // The streaming's cost on the game's thread, for BENCH.
90 let (mut uploaded, mut freed, mut upload_time, mut worst_upload) = (0usize, 0usize, 0.0f64, 0.0f64);
91 Game::new("Crabby Engine: streaming", 1280, 720)
92 .vsync(!bench)
93 .system(move |ctx: &mut Ctx| {
94 if ctx.key_pressed(Key::Escape) {
95 ctx.quit();
96 }
97 let dt = ctx.dt();
98 if ctx.key_down(Key::Left) {
99 yaw += dt * 0.8;
100 }
101 if ctx.key_down(Key::Right) {
102 yaw -= dt * 0.8;
103 }
104 if ctx.key_down(Key::W) {
105 speed = (speed + 80.0 * dt).min(600.0);
106 }
107 if ctx.key_down(Key::S) {
108 speed = (speed - 80.0 * dt).max(0.0);
109 }
110 if ctx.key_down(Key::E) {
111 altitude += 40.0 * dt;
112 }
113 if ctx.key_down(Key::Q) {
114 altitude = (altitude - 40.0 * dt).max(5.0);
115 }
116 // Fly on, keeping above the hills ahead.
117 let rotation = Quat::from_rotation_y(yaw);
118 let mut at = ctx.camera3d.position + rotation * Vec3::NEG_Z * speed * dt;
119 let ahead = at + rotation * Vec3::NEG_Z * 120.0;
120 let floor = height(at.x, at.z).max(height(ahead.x, ahead.z));
121 at.y += (floor + altitude - at.y) * (dt * 1.5).min(1.0);
122 ctx.camera3d.position = at;
123 ctx.camera3d.rotation = rotation * Quat::from_rotation_x(-0.22);
124
125 // Which tiles to start and drop.
126 cells.update(at, STARTS);
127 for &cell in cells.to_load() {
128 let corner = cells.corner(cell);
129 tiles.insert(cell, Tile::Building(Job::spawn(move || tile_data(corner))));
130 }
131 for cell in cells.to_drop() {
132 // A job still building is dropped with its result.
133 if let Some(Tile::Live(entity, tile)) = tiles.remove(cell) {
134 ctx.world.despawn(entity);
135 ctx.free_terrain_tile(tile);
136 freed += 1;
137 }
138 }
139 // Finished tiles go on the GPU, a few a frame.
140 let ground = *ground.get_or_insert_with(|| {
141 let shader = ctx.create_shader("ground", GROUND);
142 ctx.create_material(MaterialDesc {
143 shader: Some(shader),
144 ..MaterialDesc::color(Color::WHITE)
145 })
146 });
147 let mut uploads = 0;
148 for (&cell, tile) in tiles.iter_mut() {
149 if uploads == UPLOADS {
150 break;
151 }
152 if let Tile::Building(job) = tile
153 && let Some(data) = job.take()
154 {
155 let start = Instant::now();
156 let made = ctx.upload_terrain_tile(&data, ground);
157 let entity = ctx.world.spawn((Transform3D::at(cells.corner(cell)), made));
158 let took = start.elapsed().as_secs_f64() * 1000.0;
159 upload_time += took;
160 worst_upload = worst_upload.max(took);
161 uploaded += 1;
162 uploads += 1;
163 *tile = Tile::Live(entity, made);
164 }
165 }
166
167 if ctx.frame() == 400
168 && let Some(path) = &shot
169 {
170 ctx.save_screenshot(path);
171 }
172 if ctx.frame() == 402 && shot.is_some() {
173 ctx.quit();
174 }
175 if bench && ctx.frame() == 3000 {
176 let live = tiles.values().filter(|t| matches!(t, Tile::Live(..))).count();
177 println!(
178 "{uploaded} tiles uploaded ({:.2} ms each, worst {worst_upload:.2} ms), {freed} freed, {live} live; {:?}",
179 upload_time / uploaded.max(1) as f64,
180 ctx.render_stats()
181 );
182 ctx.quit();
183 }
184 })
185 .run(move |ctx| {
186 ctx.post.bloom = 0.08;
187 ctx.lighting.sun_direction = Sky::sun_direction_at(15.5);
188 ctx.lighting.shadow_distance = 500.0;
189 ctx.camera3d.far = LOAD + TILE;
190 // Fog thick enough that tiles appear behind it, not at an edge.
191 ctx.sky.fog_density = 0.0007;
192 ctx.sky.fog_height = 40.0;
193 ctx.sky.fog_falloff = 90.0;
194 ctx.camera3d.position = vec3(0.0, height(0.0, 0.0) + 60.0, 0.0);
195 });
196}Source§impl<T> Job<T>
impl<T> Job<T>
Sourcepub fn is_done(&mut self) -> bool
pub fn is_done(&mut self) -> bool
Whether the work has finished (its result may already be taken).
let mut job = Job::spawn(|| 7);
while !job.is_done() {}
assert!(job.is_done());Sourcepub fn take(&mut self) -> Option<T>
pub fn take(&mut self) -> Option<T>
The result, once the work is done; None before then, and after it
was taken. Never waits.
let mut job = Job::spawn(|| "ready");
while !job.is_done() {}
assert_eq!(job.take(), Some("ready"));
assert_eq!(job.take(), None);§Panics
If the work panicked.
Examples found in repository?
examples/streaming.rs (line 153)
82fn main() {
83 let bench = std::env::var("BENCH").is_ok();
84 let shot = std::env::var("SHOT").ok();
85 let mut cells = CellStreamer::new(TILE, LOAD, KEEP);
86 let mut tiles: HashMap<(i32, i32), Tile> = HashMap::new();
87 let mut ground: Option<Material> = None;
88 let (mut yaw, mut speed, mut altitude) = (0.4f32, if bench { 600.0f32 } else { 120.0 }, 90.0f32);
89 // The streaming's cost on the game's thread, for BENCH.
90 let (mut uploaded, mut freed, mut upload_time, mut worst_upload) = (0usize, 0usize, 0.0f64, 0.0f64);
91 Game::new("Crabby Engine: streaming", 1280, 720)
92 .vsync(!bench)
93 .system(move |ctx: &mut Ctx| {
94 if ctx.key_pressed(Key::Escape) {
95 ctx.quit();
96 }
97 let dt = ctx.dt();
98 if ctx.key_down(Key::Left) {
99 yaw += dt * 0.8;
100 }
101 if ctx.key_down(Key::Right) {
102 yaw -= dt * 0.8;
103 }
104 if ctx.key_down(Key::W) {
105 speed = (speed + 80.0 * dt).min(600.0);
106 }
107 if ctx.key_down(Key::S) {
108 speed = (speed - 80.0 * dt).max(0.0);
109 }
110 if ctx.key_down(Key::E) {
111 altitude += 40.0 * dt;
112 }
113 if ctx.key_down(Key::Q) {
114 altitude = (altitude - 40.0 * dt).max(5.0);
115 }
116 // Fly on, keeping above the hills ahead.
117 let rotation = Quat::from_rotation_y(yaw);
118 let mut at = ctx.camera3d.position + rotation * Vec3::NEG_Z * speed * dt;
119 let ahead = at + rotation * Vec3::NEG_Z * 120.0;
120 let floor = height(at.x, at.z).max(height(ahead.x, ahead.z));
121 at.y += (floor + altitude - at.y) * (dt * 1.5).min(1.0);
122 ctx.camera3d.position = at;
123 ctx.camera3d.rotation = rotation * Quat::from_rotation_x(-0.22);
124
125 // Which tiles to start and drop.
126 cells.update(at, STARTS);
127 for &cell in cells.to_load() {
128 let corner = cells.corner(cell);
129 tiles.insert(cell, Tile::Building(Job::spawn(move || tile_data(corner))));
130 }
131 for cell in cells.to_drop() {
132 // A job still building is dropped with its result.
133 if let Some(Tile::Live(entity, tile)) = tiles.remove(cell) {
134 ctx.world.despawn(entity);
135 ctx.free_terrain_tile(tile);
136 freed += 1;
137 }
138 }
139 // Finished tiles go on the GPU, a few a frame.
140 let ground = *ground.get_or_insert_with(|| {
141 let shader = ctx.create_shader("ground", GROUND);
142 ctx.create_material(MaterialDesc {
143 shader: Some(shader),
144 ..MaterialDesc::color(Color::WHITE)
145 })
146 });
147 let mut uploads = 0;
148 for (&cell, tile) in tiles.iter_mut() {
149 if uploads == UPLOADS {
150 break;
151 }
152 if let Tile::Building(job) = tile
153 && let Some(data) = job.take()
154 {
155 let start = Instant::now();
156 let made = ctx.upload_terrain_tile(&data, ground);
157 let entity = ctx.world.spawn((Transform3D::at(cells.corner(cell)), made));
158 let took = start.elapsed().as_secs_f64() * 1000.0;
159 upload_time += took;
160 worst_upload = worst_upload.max(took);
161 uploaded += 1;
162 uploads += 1;
163 *tile = Tile::Live(entity, made);
164 }
165 }
166
167 if ctx.frame() == 400
168 && let Some(path) = &shot
169 {
170 ctx.save_screenshot(path);
171 }
172 if ctx.frame() == 402 && shot.is_some() {
173 ctx.quit();
174 }
175 if bench && ctx.frame() == 3000 {
176 let live = tiles.values().filter(|t| matches!(t, Tile::Live(..))).count();
177 println!(
178 "{uploaded} tiles uploaded ({:.2} ms each, worst {worst_upload:.2} ms), {freed} freed, {live} live; {:?}",
179 upload_time / uploaded.max(1) as f64,
180 ctx.render_stats()
181 );
182 ctx.quit();
183 }
184 })
185 .run(move |ctx| {
186 ctx.post.bloom = 0.08;
187 ctx.lighting.sun_direction = Sky::sun_direction_at(15.5);
188 ctx.lighting.shadow_distance = 500.0;
189 ctx.camera3d.far = LOAD + TILE;
190 // Fog thick enough that tiles appear behind it, not at an edge.
191 ctx.sky.fog_density = 0.0007;
192 ctx.sky.fog_height = 40.0;
193 ctx.sky.fog_falloff = 90.0;
194 ctx.camera3d.position = vec3(0.0, height(0.0, 0.0) + 60.0, 0.0);
195 });
196}Trait Implementations§
Auto Trait Implementations§
impl<T> !Sync for Job<T>
impl<T> Forget for Job<T>
impl<T> Freeze for Job<T>
impl<T> RefUnwindSafe for Job<T>
impl<T> Send for Job<T>
impl<T> Unpin for Job<T>
impl<T> UnsafeUnpin for Job<T>
impl<T> UnwindSafe for Job<T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Converts
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Converts
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Converts
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Converts
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
Source§impl<T> Pointable for T
impl<T> Pointable for T
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
The inverse inclusion map: attempts to construct
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
Checks if
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
Use with care! Same as
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
The inclusion map: converts
self to the equivalent element of its superset.