Crates.io | hyperion_ec_no_s |
lib.rs | hyperion_ec_no_s |
version | 1.0.2 |
created_at | 2025-08-19 04:36:28.996921+00 |
updated_at | 2025-08-19 04:46:34.689748+00 |
description | A dense ECS without the S |
homepage | |
repository | https://gitlab.com/thatloganguy/hyperion |
max_upload_size | |
id | 1801360 |
size | 102,851 |
Named after:
The eighth-largest moon of Saturn. It is distinguished by its highly irregular shape, chaotic rotation, low density (lol), and its unusual sponge-like appearance. -- Wikipedia
I wanted a way to easily move around data for rust game dev and didn't want to manually create vectors for new structs
everytime.
This allows me to have a world in my gamestate that manages it for me and gives me access to the data everywhere.
I did not want a full ECS, go use hecs or something else if you need systems/queries and speed for accessing smaller
lego block components.
Done using the test: 'large_scale_component_iteration_speed_test_separate_timings'
Ran using: 'cargo test --release large_scale_component_iteration_speed_test_separate_timings -- --nocapture'
1.5mill entities tested - Laptop with an AMD Ryzen AI 7 350.
Loop 1: get_all_components_mut time = 13.9909ms, get_component_mut per entity time = 2.07895s
Loop 2: get_all_components_mut time = 13.8826ms, get_component_mut per entity time = 1.9981674s
Loop 3: get_all_components_mut time = 15.2029ms, get_component_mut per entity time = 2.0337959s
Loop 4: get_all_components_mut time = 14.5411ms, get_component_mut per entity time = 1.7639305s
Loop 5: get_all_components_mut time = 14.0716ms, get_component_mut per entity time = 1.762936s
Average time (get_all_components_mut): 14.33782ms
Average time (get_component_mut per entity): 1.92755596s
On the same laptop in release mode version of the bunny mark linked below I get around 240,000 bunnies at 60fps with
Y sorting and 270_000ish without.
Please note that the laptop gpu is very bad and just commenting out the texture draw call (not the loop)
lets it hit over 2.5 million bunnies still at 60fps.
https://docs.rs/hyperion_ec_no_s/latest/hyperion/
Tetra bunny mark: https://gitlab.com/thatloganguy/hyperion_bunnymark
use hyperion::prelude::*;
// Component can be derived or impl
#[derive(Component)]
struct Position(u32, u32);
#[derive(Component)]
struct Health(i32);
fn main() {
let mut world = World::new();
// Create an entity and give it some components
let e = world.create_entity();
world.add_component(e, Position(1, 2));
world.add_component(e, Health(100));
// Read mut
if let Some(pos) = world.get_component_mut::<Position>(e) {
pos.0 += 10; // mutate in place
}
// Non mut
if let Some(hp) = world.get_component::<Health>(e) {
println!("entity health = {}", hp.0);
}
// Get the owning Entity for a Position component reference
// I know this example is dumb but it's just show how it works
if let Some(pos_ref) = world.get_component::<Position>(e) {
if let Some(owner) = world.get_entity_for::<Position>(pos_ref) {
println!("owner of pos is {:?}", owner);
}
}
// Iterate over all Position components
for p in world.get_all_components_mut::<Position>() {
p.0 += 1;
p.1 += 1;
}
// Get all entities that have a Position component
let entities_with_position: Vec<_> = world.get_entities_for::<Position>();
for entity in entities_with_position {
if let Some(pos) = world.get_component::<Position>(entity) {
println!("entity {:?} has Position({}, {})", entity, pos.0, pos.1);
}
}
// Note: This is not a query/system. This is just a quicker way to get entities with multiple components.
// It is not trying to be like other ECS and only exists for qol. There are no archetypes etc.
// Get all entities that have both Position and Health,
let entities_with_pos_and_health = world.get_entities_and_components_with_2::<Position, Health>();
for (entity, pos, hp) in entities_with_pos_and_health {
println!(
"entity {:?}: Position({}, {}), Health({})",
entity, pos.0, pos.1, hp.0
);
}
// Despawn the entity (removes its components too)
world.destroy_entity(e);
// Reset the world.
world.purge();
}
use hyperion::prelude::*;
#[derive(Component)]
struct Position(u32, u32);
fn main() {
let mut world = World::new();
let mut commands = CommandBuffer::default();
// Queue: create 100 entities, each with a Position component
// You can also just queue raw entities if you want for some reason with create_entity
for _ in 0..100 {
commands.create_and_add_component(Position(0, 0));
}
// Apply queued creates; get back the Entity IDs that were created - this includes ones without components
let entities = commands.execute(&mut world);
// Queue: destroy all of them
for e in entities {
commands.destroy_entity(e);
}
// Apply queued destroys
commands.execute(&mut world);
}
use hyperion::prelude::*;
#[derive(Debug)]
struct Texture {
id: u32,
}
#[derive(Debug)]
struct Config {
max_textures: usize,
app_name: String,
}
fn main() {
let mut world = World::new();
// Make some resources and store the handles (generational IDs like entities)
let mut handles = Vec::new();
for i in 0..100 {
let texture = Texture { id: i };
let handle = world.register_resource::<Texture>(texture);
handles.push(handle);
}
// Easy to get
for handle in &handles[0..5] {
if let Some(tex) = world.get_resource::<Texture>(*handle) {
println!("Got texture with id: {}", tex.id);
}
}
// Easy to change
if let Some(tex) = world.get_resource_mut::<Texture>(handles[0]) {
tex.id = 999;
}
// Simple to remove, wow.
for handle in handles {
let removed = world.remove_resource::<Texture>(handle);
if let Some(tex) = removed {
println!("Removed texture with id: {}", tex.id);
}
}
// Register a unique Config resource - only one of a type can be registered.
// Reregistering will replace it. This actually is the same for components on entities.
world.register_unique(Config {
max_textures: 100,
app_name: "HyperionApp".to_string(),
});
// Wow so easy to grab.
if let Some(config) = world.get_unique::<Config>() {
println!("Unique Config: max_textures = {}, app_name = {}", config.max_textures, config.app_name);
}
// Don't forget mut
if let Some(config) = world.get_unique_mut::<Config>() {
config.max_textures = 200;
config.app_name = "UpdatedHyperionApp".to_string();
}
// It's changed crazy.
if let Some(config) = world.get_unique::<Config>() {
println!("Updated Unique Config: max_textures = {}, app_name = {}", config.max_textures, config.app_name);
}
// Kill it. Gets it back just in case you need it.
if let Some(removed_config) = world.remove_unique::<Config>() {
println!("Removed unique Config: {:?}", removed_config);
}
// Yeah it's gone.
assert!(world.get_unique::<Config>().is_none());
// Purge it all cause why not.
world.purge();
}
AI was used for most of the comments and generating tests because normally I don't write them. So if things aren't quite right please blame the future overlords.