Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
3691690
Inline async_executor
james7132 Jul 26, 2025
54b0bbc
Merge LocalExecutor into the Executor implementation
james7132 Jul 26, 2025
2b8ed66
Merge ThreadEecutors into the new forked async_executor
james7132 Jul 27, 2025
1121542
Move stealer queues to TLS storage. Avoid extra Arc allocations
james7132 Jul 27, 2025
01ac223
Queue tasks directly back onto local queues to avoid global injector …
james7132 Jul 28, 2025
1dab08e
Use UnsafeCell instead of RefCell
james7132 Jul 28, 2025
0b036fe
Optimize access of thread locked tasks
james7132 Jul 28, 2025
45b5e15
Clean up unnecessary unsafe use
james7132 Jul 29, 2025
ab45573
Address potential unsoundness with ThreadSpawner
james7132 Jul 30, 2025
05f1f40
Fix some CI errors
james7132 Jul 30, 2025
0f226bf
Update docs
james7132 Jul 30, 2025
9c28473
Format TOML files
james7132 Jul 30, 2025
7f8c932
Make note on ThreadLocal soundness hole
james7132 Jul 30, 2025
f84028e
Allow clippy warning
james7132 Jul 30, 2025
f44d302
Fix typos
james7132 Jul 30, 2025
227258e
Try to fix CI outside of miri
james7132 Jul 30, 2025
d88035d
Remove depnedency on concurrent-queue
james7132 Aug 2, 2025
2028736
Revert "Clean up unnecessary unsafe use"
james7132 Aug 2, 2025
da66005
Fix single-threaded builds
james7132 Aug 3, 2025
8d4e5ca
Fix builds for no_std builds
james7132 Aug 3, 2025
3cb694f
Shut up Clippy
james7132 Aug 3, 2025
d0ef3f4
Fix bevy_ecs builds
james7132 Aug 3, 2025
25233ff
Whoops
james7132 Aug 3, 2025
69a1389
Arc is only used when no_std
james7132 Aug 3, 2025
943f135
Remove now unused mentions of LocalExecutor
james7132 Aug 3, 2025
5b9a40e
Remove unused import
james7132 Aug 3, 2025
355c96f
Use pin_project_lite for web builds instead of pin_project
james7132 Aug 3, 2025
564daf6
Fix TOML formatting
james7132 Aug 3, 2025
b100634
Make CatchUnwind pin_project_lite friendly
james7132 Aug 3, 2025
8700f7e
Add missing AssertUnwindSafe
james7132 Aug 3, 2025
a94bfda
Merge branch 'main' into bevy_executor
james7132 Aug 4, 2025
6246ec8
Another attempt at fixing CI
james7132 Aug 4, 2025
6faca4f
Merge branch 'main' into bevy_executor
james7132 Aug 5, 2025
93c696b
Properly handle thread destruction and recycling
james7132 Aug 5, 2025
686d429
Switch to a solution that doesn't require TLS access in thread destru…
james7132 Aug 6, 2025
aa64f03
Try to provide a blocking solution to TaskPool::scope in single threa…
james7132 Aug 6, 2025
1d73b78
Merge branch 'main' into bevy_executor
james7132 Aug 6, 2025
79c31b7
CI fixes
james7132 Aug 8, 2025
fc875d9
Merge branch 'main' into bevy_executor
james7132 Aug 10, 2025
4dcb37c
Fix up the build, and hopefully CI
james7132 Aug 10, 2025
0923f02
Shut up Clippy
james7132 Aug 10, 2025
43a09d7
Remove test println
james7132 Aug 10, 2025
8f313f7
Fix for portable atomics
james7132 Aug 10, 2025
4a8b6b0
Fix for web builds
james7132 Aug 10, 2025
7e5cf6c
Reduce async type genreation, code indirection, and handle single-thr…
james7132 Aug 11, 2025
eb689b5
Fix docs and move expect attribute
james7132 Aug 11, 2025
7ec07af
Complete the comment
james7132 Aug 11, 2025
4a4434c
Add async_executor's tests
james7132 Aug 11, 2025
2ce5537
Run Miri on bevy_tasks in CI
james7132 Aug 11, 2025
8af9886
Make stealing a non-blocking operation
james7132 Aug 11, 2025
cfc6af4
Merge branch 'main' into bevy_executor
james7132 Aug 11, 2025
74d301c
Cache-pad the thread locals and disable multithreaded polling when th…
james7132 Aug 12, 2025
898166b
Fix Miri job to run them in sequence
james7132 Aug 12, 2025
2b96789
Fix up README and Clippy
james7132 Aug 12, 2025
f5737f7
It's clippy
james7132 Aug 12, 2025
84edd4f
Address UB in spawn_scoped_local from OOMs
james7132 Aug 15, 2025
dc7ce63
Move expect to outer block
james7132 Aug 15, 2025
b217cbd
Merge branch 'main' into bevy_executor
james7132 Aug 15, 2025
00b62c2
Fix lint
james7132 Aug 19, 2025
5d122a0
Go back to using ConcurrentQueue
james7132 Aug 19, 2025
73798b1
Don't panic
james7132 Aug 19, 2025
909f2e5
Formatting
james7132 Aug 19, 2025
f75719f
Static Executors
james7132 Aug 19, 2025
64e2e46
Cleanup
james7132 Aug 20, 2025
ba46433
Try to fix CI again
james7132 Aug 20, 2025
02fc4a5
Clippy and docs
james7132 Aug 20, 2025
96dd3d5
is_multiple_of lint
james7132 Aug 20, 2025
66c6f5f
is_multiple_of lint
james7132 Aug 20, 2025
a736240
Fix benchmarks
james7132 Aug 20, 2025
6c4f878
Merge branch 'main' into bevy_executor
james7132 Aug 22, 2025
7ea5515
Clean up dependencies for no_std builds
james7132 Aug 23, 2025
b10d226
Minimize cfg blocks
james7132 Aug 24, 2025
e8b9c1f
Try getting rid of the main thread local executor tick system
james7132 Aug 24, 2025
3354efa
Use a proper struct for Sleepers
james7132 Aug 25, 2025
c6fecbd
Update module docs
james7132 Aug 25, 2025
5df82e1
Remove unused code
james7132 Aug 25, 2025
ee8f531
Cleanup using utilities we already have
james7132 Aug 25, 2025
20fe27b
Avoid the extra Waker clone when initially scheduling tasks
james7132 Aug 28, 2025
ea262ef
Clippy, formatting, and panic documentation
james7132 Aug 28, 2025
f305276
Rename ThreadSpawner -> LocalTaskSpawner
james7132 Sep 1, 2025
5373e7c
Fix macro
james7132 Sep 1, 2025
9c35b81
Spin loop hints
james7132 Sep 4, 2025
ded5b15
Merge branch 'main' into bevy_executor
james7132 Sep 30, 2025
75a2de8
Merge branch 'bevy_executor' into static-executors
james7132 Oct 3, 2025
68a071c
Shut up clippy
james7132 Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ jobs:
miri:
runs-on: macos-latest
timeout-minutes: 60
env:
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
RUSTFLAGS: -Zrandomize-layout
# https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables
# -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time.
# -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing.
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation
steps:
- uses: actions/checkout@v5
- uses: actions/cache/restore@v4
Expand All @@ -111,17 +118,14 @@ jobs:
with:
toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
components: miri
- name: CI job
- name: CI job (Tasks)
# To run the tests one item at a time for troubleshooting, use
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_tasks --lib -- --exact
run: cargo miri test -p bevy_tasks --features bevy_executor --features multi_threaded
- name: CI job (ECS)
# To run the tests one item at a time for troubleshooting, use
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
run: cargo miri test -p bevy_ecs --features bevy_utils/debug
env:
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
RUSTFLAGS: -Zrandomize-layout
# https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables
# -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time.
# -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing.
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation

check-compiles:
runs-on: ubuntu-latest
Expand Down
4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ unused_qualifications = "warn"
[features]
default = [
"std",
"async_executor",
"android-game-activity",
"android_shared_stdcxx",
"animation",
Expand Down Expand Up @@ -566,9 +565,6 @@ custom_cursor = ["bevy_internal/custom_cursor"]
# Experimental support for nodes that are ignored for UI layouting
ghost_nodes = ["bevy_internal/ghost_nodes"]

# Uses `async-executor` as a task execution backend.
async_executor = ["std", "bevy_internal/async_executor"]

# Allows access to the `std` crate.
std = ["bevy_internal/std"]

Expand Down
4 changes: 2 additions & 2 deletions benches/benches/bevy_ecs/iteration/heavy_compute.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_ecs::prelude::*;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_tasks::{ComputeTaskPool, TaskPoolBuilder};
use criterion::Criterion;
use glam::*;

Expand All @@ -20,7 +20,7 @@ pub fn heavy_compute(c: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.bench_function("base", |b| {
ComputeTaskPool::get_or_init(TaskPool::default);
ComputeTaskPool::get_or_init(TaskPoolBuilder::default);

let mut world = World::default();

Expand Down
4 changes: 2 additions & 2 deletions benches/benches/bevy_ecs/iteration/par_iter_simple.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_ecs::prelude::*;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_tasks::{ComputeTaskPool, TaskPoolBuilder};
use glam::*;

#[derive(Component, Copy, Clone)]
Expand All @@ -26,7 +26,7 @@ fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {

impl<'w> Benchmark<'w> {
pub fn new(fragment: u16) -> Self {
ComputeTaskPool::get_or_init(TaskPool::default);
ComputeTaskPool::get_or_init(TaskPoolBuilder::default);

let mut world = World::new();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_ecs::prelude::*;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_tasks::{ComputeTaskPool, TaskPoolBuilder};
use rand::{prelude::SliceRandom, SeedableRng};
use rand_chacha::ChaCha8Rng;

Expand All @@ -18,7 +18,7 @@ pub struct Benchmark<'w>(World, QueryState<(&'w mut TableData, &'w SparseData)>)
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
ComputeTaskPool::get_or_init(TaskPool::default);
ComputeTaskPool::get_or_init(TaskPoolBuilder::default);

let mut v = vec![];
for _ in 0..100000 {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_a11y/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy", "accessibility", "a11y"]

[features]
default = ["std", "bevy_reflect", "bevy_ecs/async_executor"]
default = ["std", "bevy_reflect"]

# Functionality

Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1476,8 +1476,7 @@ type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;

fn run_once(mut app: App) -> AppExit {
while app.plugins_state() == PluginsState::Adding {
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
bevy_tasks::tick_global_task_pools_on_main_thread();
core::hint::spin_loop();
}
app.finish();
app.cleanup();
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_app/src/schedule_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ impl Plugin for ScheduleRunnerPlugin {
let plugins_state = app.plugins_state();
if plugins_state != PluginsState::Cleaned {
while app.plugins_state() == PluginsState::Adding {
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
bevy_tasks::tick_global_task_pools_on_main_thread();
core::hint::spin_loop();
}
app.finish();
app.cleanup();
Expand Down
24 changes: 3 additions & 21 deletions crates/bevy_app/src/task_pool_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,6 @@ use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuil
use core::fmt::Debug;
use log::trace;

cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] {
use {crate::Last, bevy_tasks::tick_global_task_pools_on_main_thread};
use bevy_ecs::system::NonSendMarker;

/// A system used to check and advanced our task pools.
///
/// Calls [`tick_global_task_pools_on_main_thread`],
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
fn tick_global_task_pools(_main_thread_marker: NonSendMarker) {
tick_global_task_pools_on_main_thread();
}
}
}

/// Setup of default task pools: [`AsyncComputeTaskPool`], [`ComputeTaskPool`], [`IoTaskPool`].
#[derive(Default)]
pub struct TaskPoolPlugin {
Expand All @@ -32,9 +17,6 @@ impl Plugin for TaskPoolPlugin {
fn build(&self, _app: &mut App) {
// Setup the default bevy task pools
self.task_pool_options.create_default_pools();

#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
_app.add_systems(Last, tick_global_task_pools);
}
}

Expand Down Expand Up @@ -190,7 +172,7 @@ impl TaskPoolOptions {
builder
};

builder.build()
builder
});
}

Expand Down Expand Up @@ -220,7 +202,7 @@ impl TaskPoolOptions {
builder
};

builder.build()
builder
});
}

Expand Down Expand Up @@ -250,7 +232,7 @@ impl TaskPoolOptions {
builder
};

builder.build()
builder
});
}
}
Expand Down
4 changes: 1 addition & 3 deletions crates/bevy_asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev", default-features = fa
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev", default-features = false, features = [
"uuid",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.18.0-dev", default-features = false, features = [
"async_executor",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.18.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.18.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-features = false, features = [
"std",
Expand Down
8 changes: 1 addition & 7 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ categories = ["game-engines", "data-structures"]
rust-version = "1.86.0"

[features]
default = ["std", "bevy_reflect", "async_executor", "backtrace"]
default = ["std", "bevy_reflect", "backtrace"]

# Functionality

Expand Down Expand Up @@ -49,12 +49,6 @@ bevy_debug_stepping = []
## This will often provide more detailed error messages.
track_location = []

# Executor Backend

## Uses `async-executor` as a task execution backend.
## This backend is incompatible with `no_std` targets.
async_executor = ["std", "bevy_tasks/async_executor"]

# Platform Compatibility

## Allows access to the `std` crate. Enabling this feature will prevent compilation
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ mod tests {
};
use alloc::{string::String, sync::Arc, vec, vec::Vec};
use bevy_platform::collections::HashSet;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_tasks::{ComputeTaskPool, TaskPoolBuilder};
use core::{
any::TypeId,
marker::PhantomData,
Expand Down Expand Up @@ -495,7 +495,7 @@ mod tests {

#[test]
fn par_for_each_dense() {
ComputeTaskPool::get_or_init(TaskPool::default);
ComputeTaskPool::get_or_init(TaskPoolBuilder::default);
let mut world = World::new();
let e1 = world.spawn(A(1)).id();
let e2 = world.spawn(A(2)).id();
Expand All @@ -517,7 +517,7 @@ mod tests {

#[test]
fn par_for_each_sparse() {
ComputeTaskPool::get_or_init(TaskPool::default);
ComputeTaskPool::get_or_init(TaskPoolBuilder::default);
let mut world = World::new();
let e1 = world.spawn(SparseStored(1)).id();
let e2 = world.spawn(SparseStored(2)).id();
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/query/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// #[derive(Component, PartialEq, Debug)]
/// struct A(usize);
///
/// # bevy_tasks::ComputeTaskPool::get_or_init(|| bevy_tasks::TaskPool::new());
/// # bevy_tasks::ComputeTaskPool::get_or_init(|| bevy_tasks::TaskPoolBuilder::default());
///
/// let mut world = World::new();
///
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use core::any::TypeId;
pub use self::single_threaded::SingleThreadedExecutor;

#[cfg(feature = "std")]
pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor};
pub use self::multi_threaded::{MainThreadTaskSpawner, MultiThreadedExecutor};

use fixedbitset::FixedBitSet;

Expand Down
19 changes: 8 additions & 11 deletions crates/bevy_ecs/src/schedule/executor/multi_threaded.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use alloc::{boxed::Box, vec::Vec};
use bevy_platform::cell::SyncUnsafeCell;
use bevy_platform::sync::Arc;
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor};
use bevy_tasks::{ComputeTaskPool, LocalTaskSpawner, Scope, TaskPoolBuilder};
use concurrent_queue::ConcurrentQueue;
use core::{any::Any, panic::AssertUnwindSafe};
use fixedbitset::FixedBitSet;
Expand Down Expand Up @@ -270,14 +269,12 @@ impl SystemExecutor for MultiThreadedExecutor {
}

let thread_executor = world
.get_resource::<MainThreadExecutor>()
.get_resource::<MainThreadTaskSpawner>()
.map(|e| e.0.clone());
let thread_executor = thread_executor.as_deref();

let environment = &Environment::new(self, schedule, world);

ComputeTaskPool::get_or_init(TaskPool::default).scope_with_executor(
false,
ComputeTaskPool::get_or_init(TaskPoolBuilder::default).scope_with_executor(
thread_executor,
|scope| {
let context = Context {
Expand Down Expand Up @@ -871,20 +868,20 @@ unsafe fn evaluate_and_fold_conditions(
.fold(true, |acc, res| acc && res)
}

/// New-typed [`ThreadExecutor`] [`Resource`] that is used to run systems on the main thread
/// New-typed [`LocalTaskSpawner`] [`Resource`] that is used to run systems on the main thread
#[derive(Resource, Clone)]
pub struct MainThreadExecutor(pub Arc<ThreadExecutor<'static>>);
pub struct MainThreadTaskSpawner(pub LocalTaskSpawner);

impl Default for MainThreadExecutor {
impl Default for MainThreadTaskSpawner {
fn default() -> Self {
Self::new()
}
}

impl MainThreadExecutor {
impl MainThreadTaskSpawner {
/// Creates a new executor that can be used to run systems on the main thread.
pub fn new() -> Self {
MainThreadExecutor(TaskPool::get_thread_executor())
MainThreadTaskSpawner(ComputeTaskPool::get().current_thread_spawner())
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ mod tests {
#[cfg(not(miri))]
fn parallel_execution() {
use alloc::sync::Arc;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_tasks::{ComputeTaskPool, TaskPoolBuilder};
use std::sync::Barrier;

let mut world = World::default();
let mut schedule = Schedule::default();
let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num();
let thread_count = ComputeTaskPool::get_or_init(TaskPoolBuilder::default).thread_num();

let barrier = Arc::new(Barrier::new(thread_count));

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_input/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]

[features]
default = ["std", "bevy_reflect", "bevy_ecs/async_executor", "smol_str"]
default = ["std", "bevy_reflect", "smol_str"]

# Functionality

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_input_focus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keywords = ["bevy"]
rust-version = "1.85.0"

[features]
default = ["std", "bevy_reflect", "bevy_ecs/async_executor"]
default = ["std", "bevy_reflect"]

# Functionality

Expand Down
9 changes: 0 additions & 9 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,6 @@ libm = [
"bevy_window?/libm",
]

# Uses `async-executor` as a task execution backend.
# This backend is incompatible with `no_std` targets.
async_executor = [
"std",
"bevy_tasks/async_executor",
"bevy_ecs/async_executor",
"bevy_transform/async_executor",
]

# Enables use of browser APIs.
# Note this is currently only applicable on `wasm32` architectures.
web = ["bevy_app/web", "bevy_platform/web", "bevy_reflect/web"]
Expand Down
Loading