1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult}; 6 use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, ExternalEvent, Epoch}; 7 use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint, IdNamespace}; 8 use api::{MemoryReport}; 9 use api::channel::MsgSender; 10 #[cfg(feature = "capture")] 11 use capture::CaptureConfig; 12 use frame_builder::{FrameBuilderConfig, FrameBuilder}; 13 use clip_scroll_tree::ClipScrollTree; 14 use display_list_flattener::DisplayListFlattener; 15 use intern::{Internable, Interner}; 16 use intern_types; 17 use internal_types::{FastHashMap, FastHashSet}; 18 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 19 use prim_store::{PrimitiveKeyKind}; 20 use prim_store::PrimitiveStoreStats; 21 use prim_store::borders::{ImageBorder, NormalBorderPrim}; 22 use prim_store::gradient::{LinearGradient, RadialGradient}; 23 use prim_store::image::{Image, YuvImage}; 24 use prim_store::line_dec::LineDecoration; 25 use prim_store::picture::Picture; 26 use prim_store::text_run::TextRun; 27 use resource_cache::{AsyncBlobImageInfo, FontInstanceMap}; 28 use render_backend::DocumentView; 29 use renderer::{PipelineInfo, SceneBuilderHooks}; 30 use scene::Scene; 31 use std::sync::mpsc::{channel, Receiver, Sender}; 32 use std::mem::replace; 33 use time::precise_time_ns; 34 use util::drain_filter; 35 use std::thread; 36 use std::time::Duration; 37 38 /// Represents the work associated to a transaction before scene building. 39 pub struct Transaction { 40 pub document_id: DocumentId, 41 pub display_list_updates: Vec<DisplayListUpdate>, 42 pub removed_pipelines: Vec<PipelineId>, 43 pub epoch_updates: Vec<(PipelineId, Epoch)>, 44 pub request_scene_build: Option<SceneRequest>, 45 pub blob_requests: Vec<BlobImageParams>, 46 pub blob_rasterizer: Option<(Box<AsyncBlobImageRasterizer>, AsyncBlobImageInfo)>, 47 pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, 48 pub resource_updates: Vec<ResourceUpdate>, 49 pub frame_ops: Vec<FrameMsg>, 50 pub notifications: Vec<NotificationRequest>, 51 pub set_root_pipeline: Option<PipelineId>, 52 pub render_frame: bool, 53 pub invalidate_rendered_frame: bool, 54 } 55 56 impl Transaction { can_skip_scene_builder(&self) -> bool57 pub fn can_skip_scene_builder(&self) -> bool { 58 self.request_scene_build.is_none() && 59 self.display_list_updates.is_empty() && 60 self.epoch_updates.is_empty() && 61 self.removed_pipelines.is_empty() && 62 self.blob_requests.is_empty() && 63 self.set_root_pipeline.is_none() 64 } 65 should_build_scene(&self) -> bool66 pub fn should_build_scene(&self) -> bool { 67 !self.display_list_updates.is_empty() || 68 self.set_root_pipeline.is_some() 69 } 70 rasterize_blobs(&mut self, is_low_priority: bool)71 fn rasterize_blobs(&mut self, is_low_priority: bool) { 72 if let Some((ref mut rasterizer, _)) = self.blob_rasterizer { 73 let mut rasterized_blobs = rasterizer.rasterize(&self.blob_requests, is_low_priority); 74 // try using the existing allocation if our current list is empty 75 if self.rasterized_blobs.is_empty() { 76 self.rasterized_blobs = rasterized_blobs; 77 } else { 78 self.rasterized_blobs.append(&mut rasterized_blobs); 79 } 80 } 81 } 82 } 83 84 /// Represent the remaining work associated to a transaction after the scene building 85 /// phase as well as the result of scene building itself if applicable. 86 pub struct BuiltTransaction { 87 pub document_id: DocumentId, 88 pub built_scene: Option<BuiltScene>, 89 pub resource_updates: Vec<ResourceUpdate>, 90 pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, 91 pub blob_rasterizer: Option<(Box<AsyncBlobImageRasterizer>, AsyncBlobImageInfo)>, 92 pub frame_ops: Vec<FrameMsg>, 93 pub removed_pipelines: Vec<PipelineId>, 94 pub notifications: Vec<NotificationRequest>, 95 pub interner_updates: Option<InternerUpdates>, 96 pub scene_build_start_time: u64, 97 pub scene_build_end_time: u64, 98 pub render_frame: bool, 99 pub invalidate_rendered_frame: bool, 100 } 101 102 pub struct DisplayListUpdate { 103 pub pipeline_id: PipelineId, 104 pub epoch: Epoch, 105 pub built_display_list: BuiltDisplayList, 106 pub background: Option<ColorF>, 107 pub viewport_size: LayoutSize, 108 pub content_size: LayoutSize, 109 } 110 111 /// Contains the render backend data needed to build a scene. 112 pub struct SceneRequest { 113 pub view: DocumentView, 114 pub font_instances: FontInstanceMap, 115 pub output_pipelines: FastHashSet<PipelineId>, 116 } 117 118 #[cfg(feature = "replay")] 119 pub struct LoadScene { 120 pub document_id: DocumentId, 121 pub scene: Scene, 122 pub output_pipelines: FastHashSet<PipelineId>, 123 pub font_instances: FontInstanceMap, 124 pub view: DocumentView, 125 pub config: FrameBuilderConfig, 126 pub build_frame: bool, 127 pub interners: Interners, 128 } 129 130 pub struct BuiltScene { 131 pub scene: Scene, 132 pub frame_builder: FrameBuilder, 133 pub clip_scroll_tree: ClipScrollTree, 134 } 135 136 // Message from render backend to scene builder. 137 pub enum SceneBuilderRequest { 138 Transaction(Box<Transaction>), 139 ExternalEvent(ExternalEvent), 140 DeleteDocument(DocumentId), 141 WakeUp, 142 Flush(MsgSender<()>), 143 ClearNamespace(IdNamespace), 144 SetFrameBuilderConfig(FrameBuilderConfig), 145 SimulateLongSceneBuild(u32), 146 SimulateLongLowPrioritySceneBuild(u32), 147 Stop, 148 ReportMemory(MemoryReport, MsgSender<MemoryReport>), 149 #[cfg(feature = "capture")] 150 SaveScene(CaptureConfig), 151 #[cfg(feature = "replay")] 152 LoadScenes(Vec<LoadScene>), 153 } 154 155 // Message from scene builder to render backend. 156 pub enum SceneBuilderResult { 157 Transaction(Box<BuiltTransaction>, Option<Sender<SceneSwapResult>>), 158 ExternalEvent(ExternalEvent), 159 FlushComplete(MsgSender<()>), 160 ClearNamespace(IdNamespace), 161 Stopped, 162 } 163 164 // Message from render backend to scene builder to indicate the 165 // scene swap was completed. We need a separate channel for this 166 // so that they don't get mixed with SceneBuilderRequest messages. 167 pub enum SceneSwapResult { 168 Complete(Sender<()>), 169 Aborted, 170 } 171 172 macro_rules! declare_interners { 173 ( $( $name: ident, )+ ) => { 174 /// This struct contains all items that can be shared between 175 /// display lists. We want to intern and share the same clips, 176 /// primitives and other things between display lists so that: 177 /// - GPU cache handles remain valid, reducing GPU cache updates. 178 /// - Comparison of primitives and pictures between two 179 /// display lists is (a) fast (b) done during scene building. 180 #[cfg_attr(feature = "capture", derive(Serialize))] 181 #[cfg_attr(feature = "replay", derive(Deserialize))] 182 #[derive(Default)] 183 pub struct Interners { 184 $( 185 pub $name: intern_types::$name::Interner, 186 )+ 187 } 188 189 pub struct InternerUpdates { 190 $( 191 pub $name: intern_types::$name::UpdateList, 192 )+ 193 } 194 195 impl Interners { 196 /// Reports CPU heap memory used by the interners. 197 fn report_memory( 198 &self, 199 ops: &mut MallocSizeOfOps, 200 r: &mut MemoryReport, 201 ) { 202 $( 203 r.interning.interners.$name += self.$name.size_of(ops); 204 )+ 205 } 206 207 fn end_frame_and_get_pending_updates(&mut self) -> InternerUpdates { 208 InternerUpdates { 209 $( 210 $name: self.$name.end_frame_and_get_pending_updates(), 211 )+ 212 } 213 } 214 } 215 } 216 } 217 218 enumerate_interners!(declare_interners); 219 220 // Access to `Interners` interners by `Internable` 221 pub trait InternerMut<I: Internable> 222 { interner_mut(&mut self) -> &mut Interner<I::Source, I::InternData, I::Marker>223 fn interner_mut(&mut self) -> &mut Interner<I::Source, I::InternData, I::Marker>; 224 } 225 226 macro_rules! impl_interner_mut { 227 ($($ty:ident: $mem:ident,)*) => { 228 $(impl InternerMut<$ty> for Interners { 229 fn interner_mut(&mut self) -> &mut Interner< 230 <$ty as Internable>::Source, 231 <$ty as Internable>::InternData, 232 <$ty as Internable>::Marker 233 > { 234 &mut self.$mem 235 } 236 })* 237 } 238 } 239 240 impl_interner_mut! { 241 Image: image, 242 ImageBorder: image_border, 243 LineDecoration: line_decoration, 244 LinearGradient: linear_grad, 245 NormalBorderPrim: normal_border, 246 Picture: picture, 247 PrimitiveKeyKind: prim, 248 RadialGradient: radial_grad, 249 TextRun: text_run, 250 YuvImage: yuv_image, 251 } 252 253 // A document in the scene builder contains the current scene, 254 // as well as a persistent clip interner. This allows clips 255 // to be de-duplicated, and persisted in the GPU cache between 256 // display lists. 257 struct Document { 258 scene: Scene, 259 interners: Interners, 260 prim_store_stats: PrimitiveStoreStats, 261 } 262 263 impl Document { new(scene: Scene) -> Self264 fn new(scene: Scene) -> Self { 265 Document { 266 scene, 267 interners: Interners::default(), 268 prim_store_stats: PrimitiveStoreStats::empty(), 269 } 270 } 271 } 272 273 pub struct SceneBuilder { 274 documents: FastHashMap<DocumentId, Document>, 275 rx: Receiver<SceneBuilderRequest>, 276 tx: Sender<SceneBuilderResult>, 277 api_tx: MsgSender<ApiMsg>, 278 config: FrameBuilderConfig, 279 hooks: Option<Box<SceneBuilderHooks + Send>>, 280 simulate_slow_ms: u32, 281 size_of_ops: Option<MallocSizeOfOps>, 282 } 283 284 impl SceneBuilder { new( config: FrameBuilderConfig, api_tx: MsgSender<ApiMsg>, hooks: Option<Box<SceneBuilderHooks + Send>>, size_of_ops: Option<MallocSizeOfOps>, ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>)285 pub fn new( 286 config: FrameBuilderConfig, 287 api_tx: MsgSender<ApiMsg>, 288 hooks: Option<Box<SceneBuilderHooks + Send>>, 289 size_of_ops: Option<MallocSizeOfOps>, 290 ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) { 291 let (in_tx, in_rx) = channel(); 292 let (out_tx, out_rx) = channel(); 293 ( 294 SceneBuilder { 295 documents: FastHashMap::default(), 296 rx: in_rx, 297 tx: out_tx, 298 api_tx, 299 config, 300 hooks, 301 size_of_ops, 302 simulate_slow_ms: 0, 303 }, 304 in_tx, 305 out_rx, 306 ) 307 } 308 309 /// Send a message to the render backend thread. 310 /// 311 /// We first put something in the result queue and then send a wake-up 312 /// message to the api queue that the render backend is blocking on. send(&self, msg: SceneBuilderResult)313 pub fn send(&self, msg: SceneBuilderResult) { 314 self.tx.send(msg).unwrap(); 315 let _ = self.api_tx.send(ApiMsg::WakeUp); 316 } 317 318 /// The scene builder thread's event loop. run(&mut self)319 pub fn run(&mut self) { 320 if let Some(ref hooks) = self.hooks { 321 hooks.register(); 322 } 323 324 loop { 325 match self.rx.recv() { 326 Ok(SceneBuilderRequest::WakeUp) => {} 327 Ok(SceneBuilderRequest::Flush(tx)) => { 328 self.send(SceneBuilderResult::FlushComplete(tx)); 329 } 330 Ok(SceneBuilderRequest::Transaction(mut txn)) => { 331 let built_txn = self.process_transaction(&mut txn); 332 self.forward_built_transaction(built_txn); 333 } 334 Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { 335 self.documents.remove(&document_id); 336 } 337 Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => { 338 self.config = cfg; 339 } 340 Ok(SceneBuilderRequest::ClearNamespace(id)) => { 341 self.documents.retain(|doc_id, _doc| doc_id.0 != id); 342 self.send(SceneBuilderResult::ClearNamespace(id)); 343 } 344 #[cfg(feature = "replay")] 345 Ok(SceneBuilderRequest::LoadScenes(msg)) => { 346 self.load_scenes(msg); 347 } 348 #[cfg(feature = "capture")] 349 Ok(SceneBuilderRequest::SaveScene(config)) => { 350 self.save_scene(config); 351 } 352 Ok(SceneBuilderRequest::ExternalEvent(evt)) => { 353 self.send(SceneBuilderResult::ExternalEvent(evt)); 354 } 355 Ok(SceneBuilderRequest::Stop) => { 356 self.tx.send(SceneBuilderResult::Stopped).unwrap(); 357 // We don't need to send a WakeUp to api_tx because we only 358 // get the Stop when the RenderBackend loop is exiting. 359 break; 360 } 361 Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => { 362 report += self.report_memory(); 363 tx.send(report).unwrap(); 364 } 365 Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => { 366 self.simulate_slow_ms = time_ms 367 } 368 Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(_)) => {} 369 Err(_) => { 370 break; 371 } 372 } 373 374 if let Some(ref hooks) = self.hooks { 375 hooks.poke(); 376 } 377 } 378 379 if let Some(ref hooks) = self.hooks { 380 hooks.deregister(); 381 } 382 } 383 384 #[cfg(feature = "capture")] save_scene(&mut self, config: CaptureConfig)385 fn save_scene(&mut self, config: CaptureConfig) { 386 for (id, doc) in &self.documents { 387 let interners_name = format!("interners-{}-{}", (id.0).0, id.1); 388 config.serialize(&doc.interners, interners_name); 389 } 390 } 391 392 #[cfg(feature = "replay")] load_scenes(&mut self, scenes: Vec<LoadScene>)393 fn load_scenes(&mut self, scenes: Vec<LoadScene>) { 394 for mut item in scenes { 395 self.config = item.config; 396 397 let scene_build_start_time = precise_time_ns(); 398 399 let mut built_scene = None; 400 let mut interner_updates = None; 401 402 if item.scene.has_root_pipeline() { 403 let mut clip_scroll_tree = ClipScrollTree::new(); 404 let mut new_scene = Scene::new(); 405 406 let frame_builder = DisplayListFlattener::create_frame_builder( 407 &item.scene, 408 &mut clip_scroll_tree, 409 item.font_instances, 410 &item.view, 411 &item.output_pipelines, 412 &self.config, 413 &mut new_scene, 414 &mut item.interners, 415 &PrimitiveStoreStats::empty(), 416 ); 417 418 interner_updates = Some( 419 item.interners.end_frame_and_get_pending_updates() 420 ); 421 422 built_scene = Some(BuiltScene { 423 scene: new_scene, 424 frame_builder, 425 clip_scroll_tree, 426 }); 427 } 428 429 self.documents.insert( 430 item.document_id, 431 Document { 432 scene: item.scene, 433 interners: item.interners, 434 prim_store_stats: PrimitiveStoreStats::empty(), 435 }, 436 ); 437 438 let txn = Box::new(BuiltTransaction { 439 document_id: item.document_id, 440 render_frame: item.build_frame, 441 invalidate_rendered_frame: false, 442 built_scene, 443 resource_updates: Vec::new(), 444 rasterized_blobs: Vec::new(), 445 blob_rasterizer: None, 446 frame_ops: Vec::new(), 447 removed_pipelines: Vec::new(), 448 notifications: Vec::new(), 449 scene_build_start_time, 450 scene_build_end_time: precise_time_ns(), 451 interner_updates, 452 }); 453 454 self.forward_built_transaction(txn); 455 } 456 } 457 458 /// Do the bulk of the work of the scene builder thread. process_transaction(&mut self, txn: &mut Transaction) -> Box<BuiltTransaction>459 fn process_transaction(&mut self, txn: &mut Transaction) -> Box<BuiltTransaction> { 460 if let &Some(ref hooks) = &self.hooks { 461 hooks.pre_scene_build(); 462 } 463 464 let scene_build_start_time = precise_time_ns(); 465 466 let doc = self.documents 467 .entry(txn.document_id) 468 .or_insert(Document::new(Scene::new())); 469 let scene = &mut doc.scene; 470 471 for update in txn.display_list_updates.drain(..) { 472 scene.set_display_list( 473 update.pipeline_id, 474 update.epoch, 475 update.built_display_list, 476 update.background, 477 update.viewport_size, 478 update.content_size, 479 ); 480 } 481 482 for &(pipeline_id, epoch) in &txn.epoch_updates { 483 scene.update_epoch(pipeline_id, epoch); 484 } 485 486 if let Some(id) = txn.set_root_pipeline { 487 scene.set_root_pipeline_id(id); 488 } 489 490 for pipeline_id in &txn.removed_pipelines { 491 scene.remove_pipeline(*pipeline_id) 492 } 493 494 let mut built_scene = None; 495 let mut interner_updates = None; 496 if scene.has_root_pipeline() { 497 if let Some(request) = txn.request_scene_build.take() { 498 let mut clip_scroll_tree = ClipScrollTree::new(); 499 let mut new_scene = Scene::new(); 500 501 let frame_builder = DisplayListFlattener::create_frame_builder( 502 &scene, 503 &mut clip_scroll_tree, 504 request.font_instances, 505 &request.view, 506 &request.output_pipelines, 507 &self.config, 508 &mut new_scene, 509 &mut doc.interners, 510 &doc.prim_store_stats, 511 ); 512 513 // Update the allocation stats for next scene 514 doc.prim_store_stats = frame_builder.prim_store.get_stats(); 515 516 // Retrieve the list of updates from the clip interner. 517 interner_updates = Some( 518 doc.interners.end_frame_and_get_pending_updates() 519 ); 520 521 built_scene = Some(BuiltScene { 522 scene: new_scene, 523 frame_builder, 524 clip_scroll_tree, 525 }); 526 } 527 } 528 529 let is_low_priority = false; 530 txn.rasterize_blobs(is_low_priority); 531 532 drain_filter( 533 &mut txn.notifications, 534 |n| { n.when() == Checkpoint::SceneBuilt }, 535 |n| { n.notify(); }, 536 ); 537 538 if self.simulate_slow_ms > 0 { 539 thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64)); 540 } 541 542 Box::new(BuiltTransaction { 543 document_id: txn.document_id, 544 render_frame: txn.render_frame, 545 invalidate_rendered_frame: txn.invalidate_rendered_frame, 546 built_scene, 547 rasterized_blobs: replace(&mut txn.rasterized_blobs, Vec::new()), 548 resource_updates: replace(&mut txn.resource_updates, Vec::new()), 549 blob_rasterizer: replace(&mut txn.blob_rasterizer, None), 550 frame_ops: replace(&mut txn.frame_ops, Vec::new()), 551 removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()), 552 notifications: replace(&mut txn.notifications, Vec::new()), 553 interner_updates, 554 scene_build_start_time, 555 scene_build_end_time: precise_time_ns(), 556 }) 557 } 558 559 /// Send the result of process_transaction back to the render backend. forward_built_transaction(&mut self, txn: Box<BuiltTransaction>)560 fn forward_built_transaction(&mut self, txn: Box<BuiltTransaction>) { 561 // We only need the pipeline info and the result channel if we 562 // have a hook callback *and* if this transaction actually built 563 // a new scene that is going to get swapped in. In other cases 564 // pipeline_info can be None and we can avoid some overhead from 565 // invoking the hooks and blocking on the channel. 566 let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &txn.built_scene) { 567 (&Some(ref hooks), &Some(ref built)) => { 568 let info = PipelineInfo { 569 epochs: built.scene.pipeline_epochs.clone(), 570 removed_pipelines: txn.removed_pipelines.clone(), 571 }; 572 let (tx, rx) = channel(); 573 574 hooks.pre_scene_swap(txn.scene_build_end_time - txn.scene_build_start_time); 575 576 (Some(info), Some(tx), Some(rx)) 577 } 578 _ => (None, None, None), 579 }; 580 581 let scene_swap_start_time = precise_time_ns(); 582 let has_resources_updates = !txn.resource_updates.is_empty(); 583 584 self.tx.send(SceneBuilderResult::Transaction(txn, result_tx)).unwrap(); 585 586 let _ = self.api_tx.send(ApiMsg::WakeUp); 587 588 if let Some(pipeline_info) = pipeline_info { 589 // Block until the swap is done, then invoke the hook. 590 let swap_result = result_rx.unwrap().recv(); 591 let scene_swap_time = precise_time_ns() - scene_swap_start_time; 592 self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info, scene_swap_time); 593 // Once the hook is done, allow the RB thread to resume 594 match swap_result { 595 Ok(SceneSwapResult::Complete(resume_tx)) => { 596 resume_tx.send(()).ok(); 597 }, 598 _ => (), 599 }; 600 } else if has_resources_updates { 601 if let &Some(ref hooks) = &self.hooks { 602 hooks.post_resource_update(); 603 } 604 } else { 605 if let &Some(ref hooks) = &self.hooks { 606 hooks.post_empty_scene_build(); 607 } 608 } 609 } 610 611 /// Reports CPU heap memory used by the SceneBuilder. report_memory(&mut self) -> MemoryReport612 fn report_memory(&mut self) -> MemoryReport { 613 let ops = self.size_of_ops.as_mut().unwrap(); 614 let mut report = MemoryReport::default(); 615 for doc in self.documents.values() { 616 doc.interners.report_memory(ops, &mut report); 617 } 618 619 report 620 } 621 } 622 623 /// A scene builder thread which executes expensive operations such as blob rasterization 624 /// with a lower priority than the normal scene builder thread. 625 /// 626 /// After rasterizing blobs, the secene building request is forwarded to the normal scene 627 /// builder where the FrameBuilder is generated. 628 pub struct LowPrioritySceneBuilder { 629 pub rx: Receiver<SceneBuilderRequest>, 630 pub tx: Sender<SceneBuilderRequest>, 631 pub simulate_slow_ms: u32, 632 } 633 634 impl LowPrioritySceneBuilder { run(&mut self)635 pub fn run(&mut self) { 636 loop { 637 match self.rx.recv() { 638 Ok(SceneBuilderRequest::Transaction(txn)) => { 639 let txn = self.process_transaction(txn); 640 self.tx.send(SceneBuilderRequest::Transaction(txn)).unwrap(); 641 } 642 Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { 643 self.tx.send(SceneBuilderRequest::DeleteDocument(document_id)).unwrap(); 644 } 645 Ok(SceneBuilderRequest::Stop) => { 646 self.tx.send(SceneBuilderRequest::Stop).unwrap(); 647 break; 648 } 649 Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)) => { 650 self.simulate_slow_ms = time_ms; 651 } 652 Ok(other) => { 653 self.tx.send(other).unwrap(); 654 } 655 Err(_) => { 656 break; 657 } 658 } 659 } 660 } 661 process_transaction(&mut self, mut txn: Box<Transaction>) -> Box<Transaction>662 fn process_transaction(&mut self, mut txn: Box<Transaction>) -> Box<Transaction> { 663 let is_low_priority = true; 664 txn.rasterize_blobs(is_low_priority); 665 txn.blob_requests = Vec::new(); 666 667 if self.simulate_slow_ms > 0 { 668 thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64)); 669 } 670 671 txn 672 } 673 } 674