use std::any::{TypeId, type_name};
use std::cell::Cell;
#[cfg(debug_assertions)]
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use rustc_hash::FxHashMap;
#[cfg(debug_assertions)]
pub(crate) struct BorrowTracker {
accessed: UnsafeCell<Vec<NonNull<u8>>>,
}
#[cfg(debug_assertions)]
impl BorrowTracker {
fn new() -> Self {
Self {
accessed: UnsafeCell::new(Vec::new()),
}
}
fn clear(&self) {
let ptrs = unsafe { &mut *self.accessed.get() };
ptrs.clear();
}
fn track(&self, id: ResourceId) {
let ptrs = unsafe { &mut *self.accessed.get() };
assert!(
!ptrs.contains(&id.0),
"conflicting access: resource {id} was accessed by more than one parameter \
in the same dispatch phase",
);
ptrs.push(id.0);
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ResourceId(NonNull<u8>);
impl ResourceId {
fn as_ptr(self) -> *mut u8 {
self.0.as_ptr()
}
}
unsafe impl Send for ResourceId {}
impl std::fmt::Display for ResourceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:p}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Sequence(pub(crate) i64);
impl Sequence {
pub const NULL: Self = Self(i64::MIN);
pub const UNINITIALIZED: Self = Self(-1);
pub const ZERO: Self = Self(0);
pub const fn new(value: i64) -> Self {
Self(value)
}
pub const fn get(self) -> i64 {
self.0
}
pub const fn is_null(self) -> bool {
self.0 == i64::MIN
}
pub const fn is_uninitialized(self) -> bool {
self.0 == -1
}
pub const fn next(self) -> Self {
Self(self.0.wrapping_add(1))
}
pub const fn elapsed_since(self, earlier: Self) -> i64 {
self.0.wrapping_sub(earlier.0)
}
}
impl std::fmt::Display for Sequence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub(crate) struct ResourceCell<T> {
pub(crate) value: T,
}
unsafe fn drop_resource<T>(ptr: *mut u8) {
unsafe {
let _ = Box::from_raw(ptr as *mut ResourceCell<T>);
}
}
pub struct Registry {
indices: FxHashMap<TypeId, ResourceId>,
}
impl Registry {
pub(crate) fn new() -> Self {
Self {
indices: FxHashMap::default(),
}
}
pub fn id<T: Resource>(&self) -> ResourceId {
*self
.indices
.get(&TypeId::of::<T>())
.unwrap_or_else(|| {
panic!(
"resource `{}` not registered — call WorldBuilder::register::<{}>(initial_value) during setup",
type_name::<T>(),
type_name::<T>()
)
})
}
pub fn try_id<T: Resource>(&self) -> Option<ResourceId> {
self.indices.get(&TypeId::of::<T>()).copied()
}
pub fn contains<T: Resource>(&self) -> bool {
self.indices.contains_key(&TypeId::of::<T>())
}
pub fn len(&self) -> usize {
self.indices.len()
}
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
#[cold]
pub fn check_access(&self, accesses: &[(Option<ResourceId>, &str)]) {
for i in 0..accesses.len() {
let Some(id_i) = accesses[i].0 else { continue };
for j in (i + 1)..accesses.len() {
let Some(id_j) = accesses[j].0 else { continue };
assert!(
id_i != id_j,
"conflicting access: resource borrowed by `{}` conflicts with \
resource borrowed by `{}` in the same handler",
accesses[j].1,
accesses[i].1,
);
}
}
}
}
struct DropEntry {
ptr: *mut u8,
drop_fn: unsafe fn(*mut u8),
}
pub(crate) struct Storage {
drop_entries: Vec<DropEntry>,
}
impl Storage {
pub(crate) fn new() -> Self {
Self {
drop_entries: Vec::new(),
}
}
pub(crate) fn len(&self) -> usize {
self.drop_entries.len()
}
pub(crate) fn is_empty(&self) -> bool {
self.drop_entries.is_empty()
}
}
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for Storage {}
impl Drop for Storage {
fn drop(&mut self) {
for entry in &self.drop_entries {
unsafe {
(entry.drop_fn)(entry.ptr);
}
}
}
}
pub struct WorldBuilder {
registry: Registry,
storage: Storage,
}
#[diagnostic::on_unimplemented(
message = "this type cannot be stored as a resource in the World",
note = "add `#[derive(Resource)]` to your type, or use `new_resource!` for a newtype wrapper"
)]
pub trait Resource: Send + 'static {}
#[cfg(test)]
mod resource_test_impls {
use super::Resource;
impl Resource for bool {}
impl Resource for u32 {}
impl Resource for u64 {}
impl Resource for i64 {}
impl Resource for f64 {}
impl Resource for String {}
impl<T: Send + 'static> Resource for Vec<T> {}
impl<T: Send + Sync + 'static> Resource for std::sync::Arc<T> {}
}
impl WorldBuilder {
pub fn new() -> Self {
Self {
registry: Registry::new(),
storage: Storage::new(),
}
}
#[cold]
pub fn register<T: Resource>(&mut self, value: T) -> ResourceId {
let type_id = TypeId::of::<T>();
assert!(
!self.registry.indices.contains_key(&type_id),
"resource `{}` already registered",
type_name::<T>(),
);
let cell = Box::new(ResourceCell { value });
let raw = Box::into_raw(cell) as *mut u8;
let ptr = unsafe { NonNull::new_unchecked(raw) };
let id = ResourceId(ptr);
self.registry.indices.insert(type_id, id);
self.storage.drop_entries.push(DropEntry {
ptr: raw,
drop_fn: drop_resource::<T>,
});
id
}
#[cold]
pub fn register_default<T: Default + Resource>(&mut self) -> ResourceId {
self.register(T::default())
}
#[cold]
pub fn ensure<T: Resource>(&mut self, value: T) -> ResourceId {
if let Some(id) = self.registry.try_id::<T>() {
return id;
}
self.register(value)
}
#[cold]
pub fn ensure_default<T: Default + Resource>(&mut self) -> ResourceId {
if let Some(id) = self.registry.try_id::<T>() {
return id;
}
self.register(T::default())
}
pub fn registry(&self) -> &Registry {
&self.registry
}
#[allow(dead_code)]
pub(crate) fn registry_mut(&mut self) -> &mut Registry {
&mut self.registry
}
pub fn len(&self) -> usize {
self.storage.len()
}
pub fn is_empty(&self) -> bool {
self.storage.is_empty()
}
pub fn contains<T: Resource>(&self) -> bool {
self.registry.contains::<T>()
}
pub fn install_plugin(&mut self, plugin: impl crate::plugin::Plugin) -> &mut Self {
plugin.build(self);
self
}
pub fn install_driver<D: crate::driver::Installer>(&mut self, driver: D) -> D::Poller {
driver.install(self)
}
#[allow(unused_mut)]
pub fn build(mut self) -> World {
#[cfg(feature = "reactors")]
let (reactor_notify_id, reactor_removals_id) = {
self.ensure(crate::reactor::ReactorNotify::new(16, 64));
self.ensure(crate::reactor::DeferredRemovals::default());
self.ensure(crate::reactor::SourceRegistry::new());
(
self.registry.id::<crate::reactor::ReactorNotify>(),
self.registry.id::<crate::reactor::DeferredRemovals>(),
)
};
World {
registry: self.registry,
storage: self.storage,
current_sequence: Cell::new(Sequence(0)),
shutdown: Arc::new(AtomicBool::new(false)),
_not_sync: PhantomData,
#[cfg(feature = "reactors")]
reactor_notify_id,
#[cfg(feature = "reactors")]
reactor_removals_id,
#[cfg(feature = "reactors")]
reactor_events: Some(nexus_notify::Events::with_capacity(256)),
#[cfg(debug_assertions)]
borrow_tracker: BorrowTracker::new(),
}
}
}
impl Default for WorldBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct World {
registry: Registry,
storage: Storage,
current_sequence: Cell<Sequence>,
shutdown: Arc<AtomicBool>,
_not_sync: PhantomData<Cell<()>>,
#[cfg(feature = "reactors")]
reactor_notify_id: ResourceId,
#[cfg(feature = "reactors")]
reactor_removals_id: ResourceId,
#[cfg(feature = "reactors")]
reactor_events: Option<nexus_notify::Events>,
#[cfg(debug_assertions)]
borrow_tracker: BorrowTracker,
}
impl World {
pub fn builder() -> WorldBuilder {
WorldBuilder::new()
}
pub fn registry(&self) -> &Registry {
&self.registry
}
#[allow(dead_code)]
pub(crate) fn registry_mut(&mut self) -> &mut Registry {
&mut self.registry
}
pub fn id<T: Resource>(&self) -> ResourceId {
self.registry.id::<T>()
}
pub fn try_id<T: Resource>(&self) -> Option<ResourceId> {
self.registry.try_id::<T>()
}
pub fn len(&self) -> usize {
self.storage.len()
}
pub fn is_empty(&self) -> bool {
self.storage.is_empty()
}
pub fn contains<T: Resource>(&self) -> bool {
self.registry.contains::<T>()
}
pub fn resource<T: Resource>(&self) -> &T {
let id = self.registry.id::<T>();
unsafe { self.get(id) }
}
pub fn resource_mut<T: Resource>(&mut self) -> &mut T {
let id = self.registry.id::<T>();
unsafe { self.get_mut(id) }
}
pub fn run_startup<F, Params, M>(&mut self, f: F)
where
F: crate::IntoSystem<Params, M>,
{
use crate::System;
let mut sys = f.into_system(&self.registry);
sys.run(self);
}
pub fn shutdown_handle(&self) -> crate::shutdown::ShutdownHandle {
crate::shutdown::ShutdownHandle::new(Arc::clone(&self.shutdown))
}
pub(crate) fn shutdown_flag(&self) -> &AtomicBool {
&self.shutdown
}
pub fn run(&mut self, mut f: impl FnMut(&mut World)) {
while !self.shutdown.load(std::sync::atomic::Ordering::Relaxed) {
f(self);
}
}
pub fn current_sequence(&self) -> Sequence {
self.current_sequence.get()
}
pub fn next_sequence(&mut self) -> Sequence {
let next = Sequence(self.current_sequence.get().0.wrapping_add(1));
self.current_sequence.set(next);
next
}
pub(crate) fn sequence_cell(&self) -> &Cell<Sequence> {
&self.current_sequence
}
pub fn set_sequence(&mut self, seq: Sequence) {
self.current_sequence.set(seq);
}
#[inline(always)]
pub unsafe fn get<T: 'static>(&self, id: ResourceId) -> &T {
unsafe { &(*(id.as_ptr() as *const ResourceCell<T>)).value }
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn get_mut<T: 'static>(&self, id: ResourceId) -> &mut T {
unsafe { &mut (*(id.as_ptr() as *mut ResourceCell<T>)).value }
}
#[cfg(debug_assertions)]
pub(crate) fn clear_borrows(&self) {
self.borrow_tracker.clear();
}
#[cfg(debug_assertions)]
pub(crate) fn track_borrow(&self, id: ResourceId) {
self.borrow_tracker.track(id);
}
#[cfg(feature = "reactors")]
pub fn register_source(&mut self) -> crate::reactor::DataSource {
self.resource_mut::<crate::reactor::ReactorNotify>()
.register_source()
}
#[cfg(feature = "reactors")]
pub fn spawn_reactor<C, Params, F: crate::reactor::IntoReactor<C, Params>>(
&mut self,
ctx_fn: impl FnOnce(nexus_notify::Token) -> C,
step: F,
) -> crate::reactor::ReactorRegistration<'_> {
let notify_ptr: *mut crate::reactor::ReactorNotify =
unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
let token = unsafe { &mut *notify_ptr }.create_reactor();
let ctx = ctx_fn(token);
let reactor = step.into_reactor(ctx, &self.registry);
let notify = unsafe { &mut *notify_ptr };
notify.insert_reactor(token, reactor)
}
#[cfg(feature = "reactors")]
pub fn spawn_built_reactor(
&mut self,
reactor: impl crate::reactor::Reactor + 'static,
) -> crate::reactor::ReactorRegistration<'_> {
let notify =
unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
notify.register_built(reactor)
}
#[cfg(feature = "reactors")]
pub fn dispatch_reactors(&mut self) -> bool {
let notify_ptr: *mut crate::reactor::ReactorNotify =
unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
let mut events = self
.reactor_events
.take()
.unwrap_or_else(|| nexus_notify::Events::with_capacity(256));
{
let notify = unsafe { &mut *notify_ptr };
notify.poll(&mut events);
}
let ran = !events.is_empty();
for token in events.iter() {
let idx = token.index();
let reactor = {
let notify = unsafe { &mut *notify_ptr };
notify.take_reactor(idx)
}; if let Some(mut reactor) = reactor {
reactor.run(self);
let notify = unsafe { &mut *notify_ptr };
notify.put_reactor(idx, reactor);
}
}
let removals =
unsafe { self.get_mut::<crate::reactor::DeferredRemovals>(self.reactor_removals_id) };
let mut pending = removals.take();
if !pending.is_empty() {
let notify = unsafe { &mut *notify_ptr };
while let Some(token) = pending.pop() {
notify.remove_reactor(token);
}
}
let removals =
unsafe { self.get_mut::<crate::reactor::DeferredRemovals>(self.reactor_removals_id) };
removals.put(pending);
self.reactor_events = Some(events);
ran
}
#[cfg(feature = "reactors")]
pub fn reactor_count(&self) -> usize {
self.resource::<crate::reactor::ReactorNotify>()
.reactor_count()
}
}
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for World {}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Weak};
struct Price {
value: f64,
}
impl Resource for Price {}
struct Venue {
name: &'static str,
}
impl Resource for Venue {}
struct Config {
max_orders: usize,
}
impl Resource for Config {}
#[test]
fn register_and_build() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 100.0 });
builder.register::<Venue>(Venue { name: "test" });
let world = builder.build();
#[cfg(not(feature = "reactors"))]
assert_eq!(world.len(), 2);
#[cfg(feature = "reactors")]
assert_eq!(world.len(), 5); }
#[test]
#[allow(clippy::float_cmp)]
fn resource_ids_are_distinct() {
let mut builder = WorldBuilder::new();
let id0 = builder.register::<Price>(Price { value: 0.0 });
let id1 = builder.register::<Venue>(Venue { name: "" });
let id2 = builder.register::<Config>(Config { max_orders: 0 });
assert_ne!(id0, id1);
assert_ne!(id1, id2);
assert_ne!(id0, id2);
let world = builder.build();
unsafe {
assert_eq!(world.get::<Price>(id0).value, 0.0);
assert_eq!(world.get::<Venue>(id1).name, "");
assert_eq!(world.get::<Config>(id2).max_orders, 0);
}
}
#[test]
#[allow(clippy::float_cmp)]
fn get_returns_registered_value() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 42.5 });
let world = builder.build();
let id = world.id::<Price>();
let price = unsafe { world.get::<Price>(id) };
assert_eq!(price.value, 42.5);
}
#[test]
#[allow(clippy::float_cmp)]
fn get_mut_modifies_value() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 1.0 });
let world = builder.build();
let id = world.id::<Price>();
unsafe {
world.get_mut::<Price>(id).value = 99.0;
assert_eq!(world.get::<Price>(id).value, 99.0);
}
}
#[test]
fn try_id_returns_none_for_unregistered() {
let world = WorldBuilder::new().build();
assert!(world.try_id::<Price>().is_none());
}
#[test]
fn try_id_returns_some_for_registered() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 0.0 });
let world = builder.build();
assert!(world.try_id::<Price>().is_some());
}
#[test]
#[should_panic(expected = "already registered")]
fn panics_on_duplicate_registration() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 1.0 });
builder.register::<Price>(Price { value: 2.0 });
}
#[test]
#[should_panic(expected = "not registered")]
fn panics_on_unregistered_id() {
let world = WorldBuilder::new().build();
world.id::<Price>();
}
#[test]
fn empty_builder_builds_empty_world() {
let world = WorldBuilder::new().build();
#[cfg(not(feature = "reactors"))]
assert_eq!(world.len(), 0);
#[cfg(feature = "reactors")]
assert_eq!(world.len(), 3);
}
#[test]
fn drop_runs_destructors() {
let arc = Arc::new(42u32);
let weak: Weak<u32> = Arc::downgrade(&arc);
{
let mut builder = WorldBuilder::new();
builder.register::<Arc<u32>>(arc);
let _world = builder.build();
assert!(weak.upgrade().is_some());
}
assert!(weak.upgrade().is_none());
}
#[test]
fn builder_drop_cleans_up_without_build() {
let arc = Arc::new(99u32);
let weak: Weak<u32> = Arc::downgrade(&arc);
{
let mut builder = WorldBuilder::new();
builder.register::<Arc<u32>>(arc);
}
assert!(weak.upgrade().is_none());
}
#[test]
#[allow(clippy::float_cmp)]
fn multiple_types_independent() {
let mut builder = WorldBuilder::new();
let price_id = builder.register::<Price>(Price { value: 10.0 });
let venue_id = builder.register::<Venue>(Venue { name: "CB" });
let config_id = builder.register::<Config>(Config { max_orders: 500 });
let world = builder.build();
unsafe {
assert_eq!(world.get::<Price>(price_id).value, 10.0);
assert_eq!(world.get::<Venue>(venue_id).name, "CB");
assert_eq!(world.get::<Config>(config_id).max_orders, 500);
}
}
#[test]
fn contains_reflects_registration() {
let mut builder = WorldBuilder::new();
assert!(!builder.contains::<Price>());
builder.register::<Price>(Price { value: 0.0 });
assert!(builder.contains::<Price>());
assert!(!builder.contains::<Venue>());
let world = builder.build();
assert!(world.contains::<Price>());
assert!(!world.contains::<Venue>());
}
#[test]
#[allow(clippy::float_cmp)]
fn send_to_another_thread() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 55.5 });
let world = builder.build();
let handle = std::thread::spawn(move || {
let id = world.id::<Price>();
unsafe { world.get::<Price>(id).value }
});
assert_eq!(handle.join().unwrap(), 55.5);
}
#[test]
fn registry_accessible_from_builder() {
let mut builder = WorldBuilder::new();
let registered_id = builder.register::<u64>(42);
let registry = builder.registry();
assert!(registry.contains::<u64>());
assert!(!registry.contains::<bool>());
let id = registry.id::<u64>();
assert_eq!(id, registered_id);
}
#[test]
fn registry_accessible_from_world() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(42);
let world = builder.build();
let registry = world.registry();
assert!(registry.contains::<u64>());
assert_eq!(registry.id::<u64>(), world.id::<u64>());
}
#[test]
#[allow(clippy::float_cmp)]
fn resource_reads_value() {
let mut builder = WorldBuilder::new();
builder.register::<Price>(Price { value: 42.5 });
let world = builder.build();
assert_eq!(world.resource::<Price>().value, 42.5);
}
#[test]
fn resource_mut_modifies_value() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
*world.resource_mut::<u64>() = 99;
assert_eq!(*world.resource::<u64>(), 99);
}
#[test]
fn register_default_works() {
let mut builder = WorldBuilder::new();
let id = builder.register_default::<Vec<u32>>();
let world = builder.build();
assert_eq!(id, world.id::<Vec<u32>>());
let v = world.resource::<Vec<u32>>();
assert!(v.is_empty());
}
#[test]
fn ensure_registers_new_type() {
let mut builder = WorldBuilder::new();
let id = builder.ensure::<u64>(42);
let world = builder.build();
assert_eq!(id, world.id::<u64>());
assert_eq!(*world.resource::<u64>(), 42);
}
#[test]
fn ensure_returns_existing_id() {
let mut builder = WorldBuilder::new();
let id1 = builder.register::<u64>(42);
let id2 = builder.ensure::<u64>(99);
assert_eq!(id1, id2);
let world = builder.build();
assert_eq!(*world.resource::<u64>(), 42);
}
#[test]
fn ensure_default_registers_new_type() {
let mut builder = WorldBuilder::new();
let id = builder.ensure_default::<Vec<u32>>();
let world = builder.build();
assert_eq!(id, world.id::<Vec<u32>>());
assert!(world.resource::<Vec<u32>>().is_empty());
}
#[test]
fn ensure_default_returns_existing_id() {
let mut builder = WorldBuilder::new();
builder.register::<Vec<u32>>(vec![1, 2, 3]);
let id = builder.ensure_default::<Vec<u32>>();
let world = builder.build();
assert_eq!(id, world.id::<Vec<u32>>());
assert_eq!(*world.resource::<Vec<u32>>(), vec![1, 2, 3]);
}
#[test]
fn sequence_default_is_zero() {
assert_eq!(Sequence::default(), Sequence(0));
}
#[test]
fn next_sequence_increments() {
let mut world = WorldBuilder::new().build();
assert_eq!(world.current_sequence(), Sequence(0));
world.next_sequence();
assert_eq!(world.current_sequence(), Sequence(1));
world.next_sequence();
assert_eq!(world.current_sequence(), Sequence(2));
}
#[test]
fn run_startup_dispatches_handler() {
use crate::ResMut;
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
builder.register::<bool>(false);
let mut world = builder.build();
fn init(mut counter: ResMut<u64>, mut flag: ResMut<bool>) {
*counter = 42;
*flag = true;
}
world.run_startup(init);
assert_eq!(*world.resource::<u64>(), 42);
assert!(*world.resource::<bool>());
}
#[test]
fn run_startup_multiple_phases() {
use crate::ResMut;
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn phase1(mut counter: ResMut<u64>) {
*counter += 10;
}
fn phase2(mut counter: ResMut<u64>) {
*counter += 5;
}
world.run_startup(phase1);
world.run_startup(phase2);
assert_eq!(*world.resource::<u64>(), 15);
}
#[test]
fn plugin_registers_resources() {
struct TestPlugin;
impl crate::plugin::Plugin for TestPlugin {
fn build(self, world: &mut WorldBuilder) {
world.register::<u64>(42);
world.register::<bool>(true);
}
}
let mut builder = WorldBuilder::new();
builder.install_plugin(TestPlugin);
let world = builder.build();
assert_eq!(*world.resource::<u64>(), 42);
assert!(*world.resource::<bool>());
}
#[test]
fn driver_installs_and_returns_handle() {
struct TestInstaller;
struct TestHandle {
counter_id: ResourceId,
}
impl crate::driver::Installer for TestInstaller {
type Poller = TestHandle;
fn install(self, world: &mut WorldBuilder) -> TestHandle {
let counter_id = world.register::<u64>(0);
TestHandle { counter_id }
}
}
let mut builder = WorldBuilder::new();
let handle = builder.install_driver(TestInstaller);
let world = builder.build();
unsafe {
assert_eq!(*world.get::<u64>(handle.counter_id), 0);
}
}
#[test]
fn check_access_no_conflict() {
let mut builder = WorldBuilder::new();
let id_a = builder.register::<u64>(0);
let id_b = builder.register::<u32>(0);
builder
.registry()
.check_access(&[(Some(id_a), "a"), (Some(id_b), "b")]);
}
#[test]
#[should_panic(expected = "conflicting access")]
fn check_access_detects_conflict() {
let mut builder = WorldBuilder::new();
let id = builder.register::<u64>(0);
builder
.registry()
.check_access(&[(Some(id), "a"), (Some(id), "b")]);
}
#[test]
fn sequence_wrapping() {
let builder = WorldBuilder::new();
let mut world = builder.build();
world.current_sequence.set(Sequence(i64::MAX));
assert_eq!(world.current_sequence(), Sequence(i64::MAX));
let seq = world.next_sequence();
assert_eq!(seq, Sequence(i64::MIN));
assert_eq!(world.current_sequence(), Sequence(i64::MIN));
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "conflicting access")]
fn borrow_tracker_catches_double_access() {
let mut builder = WorldBuilder::new();
let id = builder.register::<u64>(42);
let world = builder.build();
world.clear_borrows();
world.track_borrow(id);
world.track_borrow(id); }
#[cfg(debug_assertions)]
#[test]
fn borrow_tracker_allows_after_clear() {
let mut builder = WorldBuilder::new();
let id = builder.register::<u64>(42);
let world = builder.build();
world.clear_borrows();
world.track_borrow(id);
world.clear_borrows();
world.track_borrow(id); }
#[cfg(debug_assertions)]
#[test]
fn borrow_tracker_different_resources_ok() {
let mut builder = WorldBuilder::new();
let id_a = builder.register::<u64>(1);
let id_b = builder.register::<u32>(2);
let world = builder.build();
world.clear_borrows();
world.track_borrow(id_a);
world.track_borrow(id_b); }
}