1#![allow(clippy::type_complexity)]
21
22mod ext;
23pub mod input;
24pub mod meta;
25mod runner;
26use disqualified::ShortName;
27pub use ext::*;
28use input::{JobInput, JobInputItem};
29use meta::{extract_job_meta, JobMarker};
30use runner::{
31 check_job_inputs, erase_jobs, increment_time_out_frames, run_jobs, setup_time_out_frames,
32 sync_completed_jobs, sync_completed_jobs_main_world, time_out_jobs, JobResultMainWorldReceiver,
33 JobResultMainWorldSender, JobResultReceiver, JobResultSender, JobSet,
34};
35
36use core::marker::PhantomData;
37
38use bevy_app::{App, Plugin, Update};
39use bevy_ecs::{
40 component::Component,
41 event::Event,
42 query::Added,
43 schedule::{IntoSystemConfigs, IntoSystemSetConfigs},
44 system::{Commands, Query, Resource},
45 world::World,
46};
47use bevy_render::{
48 extract_resource::{ExtractResource, ExtractResourcePlugin},
49 render_resource::CommandEncoder,
50 renderer::RenderDevice,
51 sync_component::SyncComponentPlugin,
52 ExtractSchedule, Render, RenderApp, RenderSet,
53};
54use bevy_render::{sync_world::RenderEntity, Extract};
55
56pub trait GraphicsJob: Component + Clone {
69 type In: JobInput<Self>;
70
71 fn label() -> ShortName<'static> {
72 ShortName::of::<Self>()
73 }
74
75 fn run(
76 &self,
77 world: &World,
78 render_device: &RenderDevice,
79 command_encoder: &mut CommandEncoder,
80 input: JobInputItem<Self, Self::In>,
81 ) -> Result<(), JobError>;
82}
83
84#[derive(Default)]
86pub struct GraphicsJobsPlugin {
87 settings: JobExecutionSettings,
88}
89
90impl Plugin for GraphicsJobsPlugin {
91 fn build(&self, app: &mut App) {
92 app.insert_resource(self.settings);
93
94 app.add_plugins((
95 SyncComponentPlugin::<JobMarker>::default(),
96 ExtractResourcePlugin::<JobExecutionSettings>::default(),
97 ));
98
99 let (main_sender, main_receiver) = crossbeam_channel::unbounded();
100
101 app.insert_resource(JobResultMainWorldReceiver(main_receiver))
102 .add_systems(Update, sync_completed_jobs_main_world);
103
104 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
105 let (sender, receiver) = crossbeam_channel::unbounded();
106 render_app
107 .insert_resource(JobResultSender(sender))
108 .insert_resource(JobResultReceiver(receiver))
109 .insert_resource(JobResultMainWorldSender(main_sender));
110
111 render_app.add_systems(ExtractSchedule, extract_job_meta);
112
113 render_app.configure_sets(
114 Render,
115 (
116 JobSet::Setup,
117 JobSet::Check,
118 JobSet::Execute,
119 JobSet::Cleanup,
120 )
121 .chain(),
122 );
123
124 render_app.configure_sets(
125 Render,
126 (
127 JobSet::Check.after(RenderSet::Prepare),
128 JobSet::Execute.before(RenderSet::Render),
129 JobSet::Cleanup.in_set(RenderSet::Cleanup),
130 ),
131 );
132
133 render_app.add_systems(
134 Render,
135 (
136 setup_time_out_frames.in_set(JobSet::Setup),
137 check_job_inputs.in_set(JobSet::Check),
138 time_out_jobs.in_set(JobSet::Check),
139 run_jobs.in_set(JobSet::Execute),
140 increment_time_out_frames.in_set(JobSet::Cleanup),
141 sync_completed_jobs.in_set(JobSet::Cleanup),
142 ),
143 );
144 }
145 }
146}
147
148#[derive(Copy, Clone, Resource, ExtractResource)]
150pub struct JobExecutionSettings {
151 pub max_jobs_per_frame: u32,
155 pub time_out_frames: u32,
158}
159
160impl Default for JobExecutionSettings {
161 fn default() -> Self {
162 Self {
163 max_jobs_per_frame: 16,
164 time_out_frames: 16,
165 }
166 }
167}
168
169pub struct SpecializedGraphicsJobPlugin<J: GraphicsJob>(PhantomData<J>);
173
174impl<J: GraphicsJob> Default for SpecializedGraphicsJobPlugin<J> {
175 fn default() -> Self {
176 Self(PhantomData)
177 }
178}
179
180impl<J: GraphicsJob> Plugin for SpecializedGraphicsJobPlugin<J> {
181 fn build(&self, app: &mut App) {
182 app.add_plugins(<J as GraphicsJob>::In::plugin());
183
184 app.register_required_components::<J, JobMarker>();
185
186 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
187 render_app
188 .add_systems(ExtractSchedule, extract_jobs::<J>)
189 .add_systems(Render, erase_jobs::<J>.in_set(JobSet::Setup));
190 }
191 }
192}
193
194#[derive(Event, Copy, Clone, Debug)]
196pub struct JobComplete(pub Result<(), JobError>);
197
198#[derive(Copy, Clone, Debug)]
200pub enum JobError {
201 TimedOut,
205 InputsFailed,
209 ExecutionFailed,
211}
212
213fn extract_jobs<J: GraphicsJob>(
214 jobs: Extract<Query<(RenderEntity, &J), Added<JobMarker>>>,
215 mut commands: Commands,
216) {
217 let cloned_jobs = jobs
218 .iter()
219 .map(|(entity, job)| (entity, job.clone()))
220 .collect::<Vec<_>>();
221 commands.insert_batch(cloned_jobs);
222}