Skip to content

Instantly share code, notes, and snippets.

@killercup
Created July 27, 2022 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save killercup/b99a8dade4d396c08855d2dcff3cc10c to your computer and use it in GitHub Desktop.
Save killercup/b99a8dade4d396c08855d2dcff3cc10c to your computer and use it in GitHub Desktop.
Rebuilding Bevy ECS -- queries
#![allow(dead_code)]
// ------------------
// The game code
// ------------------
fn main() {
let mut app = App::new()
.add_system(example_system)
.add_system(another_example_system)
.add_system(complex_example_system);
app.world.add_entity().add(Position { x: 1., y: 4.2 });
app.run();
}
fn example_system() {
println!("foo");
}
fn another_example_system(q: Query<Position>) {
println!("bar -- query: {q:?}");
}
// TODO: Fix tuple query
fn complex_example_system(q: Query<(Position, ())>, _r: ()) {
println!("baz -- query {q:?}");
}
// ------------------
// App boilerplate
// ------------------
#[derive(Default)]
struct App {
systems: Vec<Box<dyn System>>,
world: World,
}
impl App {
fn new() -> App {
App::default()
}
fn add_system<F: IntoSystem<Params>, Params: SystemParam>(mut self, function: F) -> Self {
self.systems.push(Box::new(function.into_system()));
self
}
fn run(&mut self) {
for system in &mut self.systems {
system.run(&self.world);
}
}
}
use std::{
any::{Any, TypeId},
collections::HashMap,
};
/// Terrible entity component container
#[derive(Default)]
struct World(HashMap<Entity, HashMap<TypeId, Box<dyn Any>>>);
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
struct Entity(u32);
impl World {
fn add_entity<'world>(&'world mut self) -> EntityHandle<'world> {
let max_id = self.0.keys().map(|x| x.0).max().unwrap_or(1);
let entity = Entity(max_id);
self.0.insert(entity, HashMap::default());
EntityHandle {
world: self,
entity,
}
}
fn query<'world, T: Any>(&'world self) -> impl Iterator<Item = &'world T> {
let id = TypeId::of::<T>();
eprintln!(
"querying for {} with typeId {id:?}",
std::any::type_name::<T>()
);
self.0
.values()
.flat_map(|x| x.get(&TypeId::of::<T>()))
.filter_map(|x| x.downcast_ref::<T>())
}
}
struct EntityHandle<'world> {
world: &'world mut World,
entity: Entity,
}
impl<'world> EntityHandle<'world> {
fn add<T: Any>(&'world mut self, component: T) {
let component: Box<dyn Any> = Box::new(component);
let id = (&*component).type_id();
eprintln!(
"inserting {} with typeId {id:?}",
std::any::type_name::<T>()
);
self.world
.0
.get_mut(&self.entity)
.unwrap()
.insert(id, component);
}
}
/// Use this to fetch entities
#[derive(Debug, Clone)]
struct Query<T> {
output: Vec<T>,
}
/// The position of an entity in 2D space
#[derive(Debug, Clone)]
struct Position {
x: f32,
y: f32,
}
// ------------------
// Systems magic
// ------------------
use core::marker::PhantomData;
/// This is what we store
trait System: 'static {
fn run(&mut self, world: &World);
}
/// Convert thing to system (to create a trait object)
trait IntoSystem<Params> {
type System: System;
fn into_system(self) -> Self::System;
}
/// Convert any function with only system params into a system
impl<F, Params: SystemParam> IntoSystem<Params> for F
where
F: SystemParamFunction<Params>,
{
type System = FunctionSystem<F, Params>;
fn into_system(self) -> Self::System {
FunctionSystem {
system: self,
params: PhantomData,
}
}
}
/// Represent a system with its params
//
// TODO: do stuff with params
struct FunctionSystem<F: 'static, Params: SystemParam> {
system: F,
params: PhantomData<Params>,
}
/// Make our wrapper be a System
impl<F, Params: SystemParam> System for FunctionSystem<F, Params>
where
F: SystemParamFunction<Params>,
{
fn run(&mut self, world: &World) {
SystemParamFunction::run(&mut self.system, world);
}
}
/// Function with only system params
trait SystemParamFunction<Params: SystemParam>: 'static {
fn run(&mut self, world: &World);
}
/// unit function
impl<F> SystemParamFunction<()> for F
where
F: Fn() -> () + 'static,
{
fn run(&mut self, _: &World) {
eprintln!("calling a function with no params");
self();
}
}
/// one param function
impl<F, P1: SystemParam> SystemParamFunction<(P1,)> for F
where
F: Fn(P1) -> () + 'static,
{
fn run(&mut self, world: &World) {
eprintln!("calling a function");
eprintln!("params:");
<P1 as SystemParam>::debug();
let p1 = <P1 as SystemParam>::fetch(world);
self(p1);
}
}
/// two param function
impl<F, P1: SystemParam, P2: SystemParam> SystemParamFunction<(P1, P2)> for F
where
F: Fn(P1, P2) -> () + 'static,
{
fn run(&mut self, world: &World) {
eprintln!("calling a function");
eprintln!("params:");
<P1 as SystemParam>::debug();
<P2 as SystemParam>::debug();
let p1 = <P1 as SystemParam>::fetch(world);
let p2 = <P2 as SystemParam>::fetch(world);
self(p1, p2);
}
}
// exercise for reader: macro to make this for other functions
/// Marker trait for parameters of a System function
trait SystemParam: 'static {
fn debug();
fn fetch(world: &World) -> Self;
}
/// Query is a good param
impl<T: Clone + 'static> SystemParam for Query<T> {
fn debug() {
eprintln!("Query")
}
fn fetch(world: &World) -> Self {
Query {
output: world.query::<T>().cloned().collect(),
}
}
}
impl SystemParam for () {
fn debug() {
eprintln!("unit")
}
fn fetch(_: &World) -> Self {
()
}
}
impl<T1> SystemParam for (T1,)
where
T1: SystemParam,
{
fn debug() {
eprintln!("Tuple(");
<T1 as SystemParam>::debug();
eprintln!(")");
}
fn fetch(world: &World) -> Self {
(<T1 as SystemParam>::fetch(world),)
}
}
impl<T1, T2> SystemParam for (T1, T2)
where
T1: SystemParam,
T2: SystemParam,
{
fn debug() {
eprintln!("Tuple(");
<T1 as SystemParam>::debug();
eprintln!(", ");
<T2 as SystemParam>::debug();
eprintln!(")");
}
fn fetch(world: &World) -> Self {
(
<T1 as SystemParam>::fetch(world),
<T2 as SystemParam>::fetch(world),
)
}
}
// exercise for reader: macro to make this for other tuples
// exercise for highly-ambitious reader: 1 macro for both the other macros
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment