Skip to content

Commit 84a0226

Browse files
committed
quill: Initial commit for ECS
1 parent 6380e45 commit 84a0226

File tree

12 files changed

+1021
-0
lines changed

12 files changed

+1021
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
# Quill
1313
"quill/sys-macros",
1414
"quill/sys",
15+
"quill/ecs",
1516
"quill/common",
1617
"quill/api",
1718
"quill/plugin-format",

quill/ecs/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "quill-ecs"
3+
version = "0.1.0"
4+
authors = ["caelunshun <[email protected]>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
bytemuck = "1"

quill/ecs/src/component.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::{alloc::Layout, any::TypeId, sync::Arc};
2+
3+
use crate::space::MemorySpace;
4+
5+
/// Type ID of a component.
6+
///
7+
/// Supports both Rust types and arbitrary
8+
/// "opaque" types identified by a `u64`.
9+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
10+
pub struct ComponentTypeId(ComponentTypeIdInner);
11+
12+
impl ComponentTypeId {
13+
pub fn of<T: 'static>() -> Self {
14+
Self(ComponentTypeIdInner::Rust(TypeId::of::<T>()))
15+
}
16+
17+
pub fn opaque(id: u64) -> Self {
18+
Self(ComponentTypeIdInner::Opaque(id))
19+
}
20+
}
21+
22+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
23+
enum ComponentTypeIdInner {
24+
Rust(TypeId),
25+
Opaque(u64),
26+
}
27+
28+
/// Metadata for a component type.
29+
#[derive(Clone)]
30+
pub struct ComponentMeta {
31+
/// Memory space where the component is allocated.
32+
pub(crate) space: Arc<MemorySpace>,
33+
/// Component type ID.
34+
pub(crate) type_id: ComponentTypeId,
35+
/// Component layout.
36+
pub(crate) layout: Layout,
37+
/// Function to drop the component.
38+
pub(crate) drop_fn: Arc<dyn Fn(*mut u8) + Send + Sync>,
39+
}
40+
41+
impl ComponentMeta {
42+
/// Creates a `ComponentMeta` for a native Rust component.
43+
pub fn of<T: 'static>() -> Self {
44+
Self {
45+
space: Arc::new(MemorySpace::host()),
46+
type_id: ComponentTypeId::of::<T>(),
47+
layout: Layout::new::<T>(),
48+
drop_fn: Arc::new(|data| unsafe { std::ptr::drop_in_place(data.cast::<T>()) }),
49+
}
50+
}
51+
52+
/// Creates a `ComponentMeta` for an arbitrary type, maybe opaque,
53+
/// maybe allocated in a non-default memory space.
54+
pub fn custom(
55+
space: Arc<MemorySpace>,
56+
type_id: ComponentTypeId,
57+
layout: Layout,
58+
drop_fn: Arc<dyn Fn(*mut u8) + Send + Sync>,
59+
) -> Self {
60+
Self {
61+
space,
62+
type_id,
63+
layout,
64+
drop_fn,
65+
}
66+
}
67+
}

quill/ecs/src/entity.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
2+
pub struct EntityId {
3+
index: u32,
4+
generation: u32,
5+
}
6+
7+
impl EntityId {
8+
pub fn to_bits(self) -> u64 {
9+
((self.index as u64) << 32) | (self.generation as u64)
10+
}
11+
12+
pub fn from_bits(bits: u64) -> Self {
13+
let index = (bits >> 32) as u32;
14+
let generation = bits as u32;
15+
Self { index, generation }
16+
}
17+
18+
pub fn index(self) -> u32 {
19+
self.index
20+
}
21+
22+
pub fn generation(self) -> u32 {
23+
self.generation
24+
}
25+
}

quill/ecs/src/layout_ext.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::alloc::Layout;
2+
3+
/// Provides extra methods on `Layout` that are unstable
4+
/// in `std`.
5+
pub trait LayoutExt: Sized {
6+
fn repeat(&self, n: usize) -> Result<(Self, usize), ()>;
7+
8+
fn padding_needed_for(&self, align: usize) -> usize;
9+
}
10+
11+
// Implementations taken from `std`.
12+
impl LayoutExt for Layout {
13+
fn repeat(&self, n: usize) -> Result<(Self, usize), ()> {
14+
// This cannot overflow. Quoting from the invariant of Layout:
15+
// > `size`, when rounded up to the nearest multiple of `align`,
16+
// > must not overflow (i.e., the rounded value must be less than
17+
// > `usize::MAX`)
18+
let padded_size = self.size() + <Self as LayoutExt>::padding_needed_for(self, self.align());
19+
let alloc_size = padded_size.checked_mul(n).ok_or(())?;
20+
21+
// SAFETY: self.align is already known to be valid and alloc_size has been
22+
// padded already.
23+
unsafe {
24+
Ok((
25+
Layout::from_size_align_unchecked(alloc_size, self.align()),
26+
padded_size,
27+
))
28+
}
29+
}
30+
31+
fn padding_needed_for(&self, align: usize) -> usize {
32+
let len = self.size();
33+
34+
// Rounded up value is:
35+
// len_rounded_up = (len + align - 1) & !(align - 1);
36+
// and then we return the padding difference: `len_rounded_up - len`.
37+
//
38+
// We use modular arithmetic throughout:
39+
//
40+
// 1. align is guaranteed to be > 0, so align - 1 is always
41+
// valid.
42+
//
43+
// 2. `len + align - 1` can overflow by at most `align - 1`,
44+
// so the &-mask with `!(align - 1)` will ensure that in the
45+
// case of overflow, `len_rounded_up` will itself be 0.
46+
// Thus the returned padding, when added to `len`, yields 0,
47+
// which trivially satisfies the alignment `align`.
48+
//
49+
// (Of course, attempts to allocate blocks of memory whose
50+
// size and padding overflow in the above manner should cause
51+
// the allocator to yield an error anyway.)
52+
53+
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
54+
len_rounded_up.wrapping_sub(len)
55+
}
56+
}

quill/ecs/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![allow(unused)] // TEMP (remove before merge)
2+
#![allow(unstable_name_collisions)]
3+
4+
mod layout_ext;
5+
mod space;
6+
mod storage;
7+
mod entity;
8+
mod component;

quill/ecs/src/space.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::alloc::Layout;
2+
3+
/// A memory space used to store components.
4+
///
5+
/// The default memory space just allocates memory on the host.
6+
pub struct MemorySpace {
7+
alloc: Box<dyn Fn(Layout) -> *mut u8 + Send + Sync>,
8+
dealloc: Box<dyn Fn(*mut u8, Layout) + Send + Sync>,
9+
}
10+
11+
impl MemorySpace {
12+
pub fn host() -> Self {
13+
unsafe {
14+
Self::new(
15+
|layout| std::alloc::alloc(layout),
16+
|ptr, layout| std::alloc::dealloc(ptr, layout),
17+
)
18+
}
19+
}
20+
21+
pub unsafe fn new(
22+
alloc: impl Fn(Layout) -> *mut u8 + Send + Sync + 'static,
23+
dealloc: impl Fn(*mut u8, Layout) + Send + Sync + 'static,
24+
) -> Self {
25+
Self {
26+
alloc: Box::new(alloc),
27+
dealloc: Box::new(dealloc),
28+
}
29+
}
30+
31+
pub(crate) unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
32+
(self.alloc)(layout)
33+
}
34+
35+
pub(crate) unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
36+
(self.dealloc)(ptr, layout)
37+
}
38+
}

quill/ecs/src/storage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod blob_vec;
2+
mod sparse_set;

0 commit comments

Comments
 (0)