# Hyperion
## A dense ECS without the S.
\
**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
## Quick things:
- No systems
- No queries
- No archetypes
- Simply component storage managers and a world that handles it with generational ID handles
- Use the command buffer to queue up changes you set up in your loops so they don't crash and burn.
- A resource registry to store things like textures, sounds etc. in the world for easy access via handles.
- Unique resources in the resource registry accessible just via their types.
## Why:
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.
## Test Speed:
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.
## Docs
https://docs.rs/hyperion_ec_no_s/latest/hyperion/
## Examples:
Tetra bunny mark: https://gitlab.com/thatloganguy/hyperion_bunnymark
### Basic usage:
```rust
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();
}
```
### Command Buffer:
```rust
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);
}
```
### Resource Registry:
```rust
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();
}
```
### Final notes
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.