Skip to content

Commit

Permalink
Adding functions to decompose an EntityAny into a (u32, u32) pair and…
Browse files Browse the repository at this point in the history
… recompose an EntityAny from one. This is intended primarily for applications like FFI, so that code in other languages can hold entity handles.
  • Loading branch information
recatek committed Oct 15, 2024
1 parent b3b4614 commit 3740f7d
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 1 deletion.
28 changes: 28 additions & 0 deletions src/entity.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::num::NonZeroU32;

#[cfg(debug_assertions)]
use std::fmt::{Debug, Formatter, Result as FmtResult};
Expand Down Expand Up @@ -171,6 +172,33 @@ impl EntityAny {
Self { key, version }
}

/// Creates a new entity handle from raw inner data. Useful for applications like FFI.
///
/// # Safety
///
/// Despite the use of raw unchecked values, this is still an inherently memory-safe
/// operation and produces a memory-safe handle. Entity access in gecs is validated
/// to prevent issues from bad raw entity creation and other bugs like using an entity
/// handle from a different ECS world. While altering the values in a key might cause
/// access to unexpected data, it won't cause access to uninitialized memory, invalid
/// entities, or result in other undefined behavior.
#[inline(always)]
pub fn from_raw(raw: (u32, u32)) -> Result<Self, EcsError> {
let (key, version) = raw;
// This really only checks that the version is nonzero. All other key/versions are "valid".
let version = SlotVersion::new(NonZeroU32::new(version).ok_or(EcsError::InvalidRawEntity)?);
Ok(Self { key, version })
}

/// Returns the raw inner data of this entity handle. Useful for applications like FFI.
///
/// The data returned has no inherent value and should not be modified or reordered.
/// The only use for this data is for creating a handle using [`EntityAny::from_raw`].
#[inline(always)]
pub fn raw(&self) -> (u32, u32) {
(self.key, self.version.get().get())
}

#[inline(always)]
pub(crate) fn slot_index(&self) -> TrimmedIndex {
unsafe {
Expand Down
5 changes: 4 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use std::fmt::{Display, Formatter, Result};
/// Error reporting enum for ECS operation failure.
#[derive(Debug)]
pub enum EcsError {
/// A runtime entity type did not meet the expected type for this operation.
/// A runtime entity did not meet the expected type for this operation.
InvalidEntityType,
/// We failed to construct a locally-valid entity handle from raw data.
InvalidRawEntity,
}

impl std::error::Error for EcsError {}
Expand All @@ -14,6 +16,7 @@ impl Display for EcsError {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
EcsError::InvalidEntityType => write!(f, "invalid type for entity"),
EcsError::InvalidRawEntity => write!(f, "invalid raw entity data"),
}
}
}
7 changes: 7 additions & 0 deletions src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ pub struct ArchetypeVersion {
}

impl SlotVersion {
#[inline(always)]
pub(crate) fn new(version: NonZeroU32) -> Self {
Self {
version, // Direct set
}
}

#[inline(always)]
pub(crate) fn start() -> Self {
Self {
Expand Down
29 changes: 29 additions & 0 deletions tests/test_raw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use gecs::prelude::*;

#[derive(Debug, PartialEq)]
pub struct CompA(pub u32);

ecs_world! {
ecs_archetype!(
ArchFoo,
CompA,
);
}

#[test]
fn test_raw() {
let mut world = EcsWorld::default();

// Filler to check for bad region access
world.create::<ArchFoo>((CompA(0xFEEEEEED),));

let entity = world.create::<ArchFoo>((CompA(0xDEADBEEF),));

// Filler to check for bad region access
world.create::<ArchFoo>((CompA(0xBEEEEEEF),));

assert_eq!(ecs_find!(world, entity, |a: &CompA| a.0), Some(0xDEADBEEF));

let entity = EntityAny::from_raw(entity.into_any().raw()).unwrap();
assert_eq!(ecs_find!(world, entity, |a: &CompA| a.0), Some(0xDEADBEEF));
}

0 comments on commit 3740f7d

Please sign in to comment.