Skip to content

Commit

Permalink
add thread local resources (bevyengine#671)
Browse files Browse the repository at this point in the history
  • Loading branch information
cart authored and joshuajbouw committed Oct 24, 2020
1 parent 2892067 commit f63a16d
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 40 deletions.
18 changes: 18 additions & 0 deletions crates/bevy_app/src/app_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ impl AppBuilder {
self
}

pub fn add_thread_local_resource<T>(&mut self, resource: T) -> &mut Self
where
T: 'static,
{
self.app.resources.insert_thread_local(resource);
self
}

pub fn init_resource<R>(&mut self) -> &mut Self
where
R: FromResources + Send + Sync + 'static,
Expand All @@ -237,6 +245,16 @@ impl AppBuilder {
self
}

pub fn init_thread_local_resource<R>(&mut self) -> &mut Self
where
R: FromResources + 'static,
{
let resource = R::from_resources(&self.app.resources);
self.app.resources.insert_thread_local(resource);

self
}

pub fn set_runner(&mut self, run_fn: impl Fn(App) + 'static) -> &mut Self {
self.app.runner = Box::new(run_fn);
self
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_ecs/hecs/src/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ use core::{

use crate::{archetype::Archetype, Component, MissingComponent};

/// Atomically enforces Rust-style borrow checking at runtime
#[derive(Debug)]
pub struct AtomicBorrow(AtomicUsize);

impl AtomicBorrow {
/// Creates a new AtomicBorrow
pub const fn new() -> Self {
Self(AtomicUsize::new(0))
}

/// Starts a new immutable borrow. This can be called any number of times
pub fn borrow(&self) -> bool {
let value = self.0.fetch_add(1, Ordering::Acquire).wrapping_add(1);
if value == 0 {
Expand All @@ -44,18 +47,21 @@ impl AtomicBorrow {
}
}

/// Starts a new mutable borrow. This must be unique. It cannot be done in parallel with other borrows or borrow_muts
pub fn borrow_mut(&self) -> bool {
self.0
.compare_exchange(0, UNIQUE_BIT, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}

/// Release an immutable borrow.
pub fn release(&self) {
let value = self.0.fetch_sub(1, Ordering::Release);
debug_assert!(value != 0, "unbalanced release");
debug_assert!(value & UNIQUE_BIT == 0, "shared release of unique borrow");
}

/// Release a mutable borrow.
pub fn release_mut(&self) {
let value = self.0.fetch_and(!UNIQUE_BIT, Ordering::Release);
debug_assert_ne!(value & UNIQUE_BIT, 0, "unique release of shared borrow");
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/hecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ mod serde;
mod world;

pub use archetype::{Archetype, TypeState};
pub use borrow::{Ref, RefMut};
pub use borrow::{AtomicBorrow, Ref, RefMut};
pub use bundle::{Bundle, DynamicBundle, MissingComponent};
pub use entities::{Entity, EntityReserver, Location, NoSuchEntity};
pub use entity_builder::{BuiltEntity, EntityBuilder};
Expand Down
245 changes: 242 additions & 3 deletions crates/bevy_ecs/src/resource/resources.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
use super::{FetchResource, ResourceQuery};
use crate::system::SystemId;
use bevy_hecs::{Archetype, Entity, Ref, RefMut, TypeInfo, TypeState};
use bevy_hecs::{Archetype, AtomicBorrow, Entity, Ref, RefMut, TypeInfo, TypeState};
use bevy_utils::HashMap;
use core::any::TypeId;
use std::ptr::NonNull;
use downcast_rs::{impl_downcast, Downcast};
use std::{
fmt::Debug,
ops::{Deref, DerefMut},
ptr::NonNull,
thread::ThreadId,
};

/// A Resource type
pub trait Resource: Send + Sync + 'static {}
Expand All @@ -22,17 +28,103 @@ pub enum ResourceIndex {
System(SystemId),
}

// TODO: consider using this for normal resources (would require change tracking)
trait ResourceStorage: Downcast {}
impl_downcast!(ResourceStorage);

struct StoredResource<T: 'static> {
value: T,
atomic_borrow: AtomicBorrow,
}

pub struct VecResourceStorage<T: 'static> {
stored: Vec<StoredResource<T>>,
}

impl<T: 'static> VecResourceStorage<T> {
fn get(&self, index: usize) -> Option<ResourceRef<'_, T>> {
self.stored
.get(index)
.map(|stored| ResourceRef::new(&stored.value, &stored.atomic_borrow))
}

fn get_mut(&self, index: usize) -> Option<ResourceRefMut<'_, T>> {
self.stored.get(index).map(|stored|
// SAFE: ResourceRefMut ensures that this borrow is unique
unsafe {
let value = &stored.value as *const T as *mut T;
ResourceRefMut::new(&mut *value, &stored.atomic_borrow)
})
}

fn push(&mut self, resource: T) {
self.stored.push(StoredResource {
atomic_borrow: AtomicBorrow::new(),
value: resource,
})
}

fn set(&mut self, index: usize, resource: T) {
self.stored[index].value = resource;
}

fn is_empty(&self) -> bool {
self.stored.is_empty()
}
}

impl<T: 'static> Default for VecResourceStorage<T> {
fn default() -> Self {
Self {
stored: Default::default(),
}
}
}

impl<T: 'static> ResourceStorage for VecResourceStorage<T> {}

/// A collection of resource instances identified by their type.
#[derive(Debug, Default)]
pub struct Resources {
pub(crate) resource_data: HashMap<TypeId, ResourceData>,
thread_local_data: HashMap<TypeId, Box<dyn ResourceStorage>>,
main_thread_id: ThreadId,
}

impl Default for Resources {
fn default() -> Self {
Resources {
resource_data: Default::default(),
thread_local_data: Default::default(),
main_thread_id: std::thread::current().id(),
}
}
}

impl Resources {
pub fn insert<T: Resource>(&mut self, resource: T) {
self.insert_resource(resource, ResourceIndex::Global);
}

pub fn insert_thread_local<T: 'static>(&mut self, resource: T) {
self.check_thread_local();
let entry = self
.thread_local_data
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(VecResourceStorage::<T>::default()));
let resources = entry.downcast_mut::<VecResourceStorage<T>>().unwrap();
if resources.is_empty() {
resources.push(resource);
} else {
resources.set(0, resource);
}
}

fn check_thread_local(&self) {
if std::thread::current().id() != self.main_thread_id {
panic!("Attempted to access a thread local resource off of the main thread.")
}
}

pub fn contains<T: Resource>(&self) -> bool {
self.get_resource::<T>(ResourceIndex::Global).is_some()
}
Expand All @@ -45,6 +137,26 @@ impl Resources {
self.get_resource_mut(ResourceIndex::Global)
}

pub fn get_thread_local<T: 'static>(&self) -> Option<ResourceRef<'_, T>> {
self.check_thread_local();
self.thread_local_data
.get(&TypeId::of::<T>())
.and_then(|storage| {
let resources = storage.downcast_ref::<VecResourceStorage<T>>().unwrap();
resources.get(0)
})
}

pub fn get_thread_local_mut<T: 'static>(&self) -> Option<ResourceRefMut<'_, T>> {
self.check_thread_local();
self.thread_local_data
.get(&TypeId::of::<T>())
.and_then(|storage| {
let resources = storage.downcast_ref::<VecResourceStorage<T>>().unwrap();
resources.get_mut(0)
})
}

/// Returns a clone of the underlying resource, this is helpful when borrowing something
/// cloneable (like a task pool) without taking a borrow on the resource map
pub fn get_cloned<T: Resource + Clone>(&self) -> Option<T> {
Expand Down Expand Up @@ -281,6 +393,93 @@ where
}
}

/// Shared borrow of an entity's component
#[derive(Clone)]
pub struct ResourceRef<'a, T: 'static> {
borrow: &'a AtomicBorrow,
resource: &'a T,
}

impl<'a, T: 'static> ResourceRef<'a, T> {
/// Creates a new resource borrow
pub fn new(resource: &'a T, borrow: &'a AtomicBorrow) -> Self {
borrow.borrow();
Self { resource, borrow }
}
}

unsafe impl<T: 'static> Send for ResourceRef<'_, T> {}
unsafe impl<T: 'static> Sync for ResourceRef<'_, T> {}

impl<'a, T: 'static> Drop for ResourceRef<'a, T> {
fn drop(&mut self) {
self.borrow.release()
}
}

impl<'a, T: 'static> Deref for ResourceRef<'a, T> {
type Target = T;

fn deref(&self) -> &T {
self.resource
}
}

impl<'a, T: 'static> Debug for ResourceRef<'a, T>
where
T: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.deref().fmt(f)
}
}

/// Unique borrow of a resource
pub struct ResourceRefMut<'a, T: 'static> {
borrow: &'a AtomicBorrow,
resource: &'a mut T,
}

impl<'a, T: 'static> ResourceRefMut<'a, T> {
/// Creates a new entity component mutable borrow
pub fn new(resource: &'a mut T, borrow: &'a AtomicBorrow) -> Self {
borrow.borrow_mut();
Self { resource, borrow }
}
}

unsafe impl<T: 'static> Send for ResourceRefMut<'_, T> {}
unsafe impl<T: 'static> Sync for ResourceRefMut<'_, T> {}

impl<'a, T: 'static> Drop for ResourceRefMut<'a, T> {
fn drop(&mut self) {
self.borrow.release_mut();
}
}

impl<'a, T: 'static> Deref for ResourceRefMut<'a, T> {
type Target = T;

fn deref(&self) -> &T {
self.resource
}
}

impl<'a, T: 'static> DerefMut for ResourceRefMut<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.resource
}
}

impl<'a, T: 'static> Debug for ResourceRefMut<'a, T>
where
T: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.deref().fmt(f)
}
}

#[cfg(test)]
mod tests {
use super::Resources;
Expand Down Expand Up @@ -335,4 +534,44 @@ mod tests {
let _x = resources.get_mut::<i32>();
let _y = resources.get_mut::<i32>();
}

#[test]
fn thread_local_resource() {
let mut resources = Resources::default();
resources.insert_thread_local(123i32);
resources.insert_thread_local(456i64);
assert_eq!(*resources.get_thread_local::<i32>().unwrap(), 123);
assert_eq!(*resources.get_thread_local_mut::<i64>().unwrap(), 456);
}

#[test]
fn thread_local_resource_ref_aliasing() {
let mut resources = Resources::default();
resources.insert_thread_local(123i32);
let a = resources.get_thread_local::<i32>().unwrap();
let b = resources.get_thread_local::<i32>().unwrap();
assert_eq!(*a, 123);
assert_eq!(*b, 123);
}

#[test]
#[should_panic]
fn thread_local_resource_mut_ref_aliasing() {
let mut resources = Resources::default();
resources.insert_thread_local(123i32);
let _a = resources.get_thread_local::<i32>().unwrap();
let _b = resources.get_thread_local_mut::<i32>().unwrap();
}

#[test]
#[should_panic]
fn thread_local_resource_panic() {
let mut resources = Resources::default();
resources.insert_thread_local(0i32);
std::thread::spawn(move || {
let _ = resources.get_thread_local_mut::<i32>();
})
.join()
.unwrap();
}
}
Loading

0 comments on commit f63a16d

Please sign in to comment.