Skip to content

Instantly share code, notes, and snippets.

@sketchpunk
Created June 3, 2019 18:19
Show Gist options
  • Save sketchpunk/54a745786e0db0d4fadae346e6fecc1c to your computer and use it in GitHub Desktop.
Save sketchpunk/54a745786e0db0d4fadae346e6fecc1c to your computer and use it in GitHub Desktop.
Prototype for Handling Multiple Storage Types for ECS
#![allow(non_snake_case)]
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]
#![allow(unused_mut)]
use std::fmt::Debug;
use std::cell::{ RefCell, RefMut, Ref };
use std::collections::HashMap;
use std::any::{ TypeId, Any };
fn main() {
let mut st = Storages::new();
st.reg::<Pos>(); // Test Creating new store based on Pos and DenseVec<Pos>
st.rm::<Pos>(); // Test if able to access DenseVec<Pos>.remove() function
st.get::<Pos>(); // Test if able to cast all the way down to IStorage access to DenseVec<Pos>
st.rm2(); // Same as st.rm but without passing in type, see if can still access the final remove function.
}
//###################################################################
#[derive(Debug, Default)]
pub struct Pos{
pub x : f32,
pub y : f32,
}
impl Pos{
pub fn new() -> Self{ Pos{ x:0.0, y:0.0, } }
pub fn from( x: f32, y:f32) -> Self{ Pos{ x, y, } }
}
impl ICom for Pos{
type Storage = DenseVec<Self>;
}
//###################################################################
// Main Storage Object
pub struct Storages{
stores: HashMap< TypeId, Box<IStore> >,
}
impl Storages{
pub fn new() -> Self{
Storages{ stores: HashMap::new() }
}
// Create a new store based on Type and Its Storage Type
pub fn reg<T:ICom>( &mut self ){
let t_id = TypeId::of::<T>();
let store : T::Storage = Default::default();
println!("{:?}", store);
self.stores.insert( t_id, Box::new(
//Store::<T>{ inner: RefCell::new(store) }
Store::<T>::new( store ) // Cleaner
));
}
// How to get access to the data
pub fn get<T:ICom>( &self ){
let t_id = TypeId::of::<T>();
let a = self.stores.get( &t_id ).unwrap();
let b = a.as_any();
let c = b.downcast_ref::< Store<T> >().unwrap();
let d = c.inner.borrow();
d.get();
println!("{:?}", d );
}
// How to remove elements without all the casting nonsense.
pub fn rm<T:ICom>( &mut self ){
println!("Storages.rm");
let t_id = TypeId::of::<T>();
let a = self.stores.get_mut( &t_id ).unwrap();
a.rm();
//println!("{:?}", a );
}
// Removing without casting and generic nonsense.
pub fn rm2( &mut self ){
let a = self.stores.get_mut( &TypeId::of::<Pos>() ).unwrap();
a.rm();
}
}
//###################################################################
// Middle Man object between Storages and IStorage, help with casting and interal mutability.
pub struct Store<T:ICom>{ inner: RefCell< T::Storage > }
impl<T:ICom> Store<T>{
fn new( inner: T::Storage ) -> Self{
Store{ inner: RefCell::new( inner ) }
}
}
// Main Trait object to be stored in storages. Used for casting from Any and calling
// any non generic functionality
pub trait IStore{
fn as_any( &self ) -> &dyn Any;
fn rm( &mut self );
}
impl<T:ICom> IStore for Store<T>{
fn as_any( &self ) -> &dyn Any{ self }
fn rm( &mut self ){
println!("IStore Rm");
self.inner.borrow_mut().remove( );
}
}
//###################################################################
// All components need to implement this, This is so we can easily handle creating storage types.
pub trait ICom: Debug + Sized + 'static{
type Storage: IStorage<Self> + Default + std::fmt::Debug;
}
// Main Trait that all storage types needs to implement. Should contain all the methods you'd ever
// need to manage the data.
pub trait IStorage<T:ICom>{
fn get( &self );
fn remove( &mut self );
}
// Exampl of a Storage Type. To start there will be a Dense and Sparse Vector Objects.
#[derive(Debug, Default)]
pub struct DenseVec<T>{ data: Vec<T> }
impl<T:ICom> IStorage<T> for DenseVec<T>{
fn get( &self ){
println!("DenseVec.get");
}
fn remove( &mut self ){
println!("DenseVec.remove");
}
}
/*
**NOTES**
Storages is the Main Container for all the stores
- It uses a hashmap as its container, TypeID is the Key and the Value is a non-generic Trait IStore.
- For this instance, can not use a generic trait as a trait object, so a wrapper object is needed.
Store<T> is the wrapper object for all the data stores
- It implements IStore so that it can be saved in Storage's Value Box as a Trait Object
- Store wraps T::Storage in a RefCell to for internal mutability
- Store also has a Remove Function that can be called on its inner storage object.
The idea here is to be able to call the delete function on the store without needing
to know T. Since IStore/Store was created knowing T, So IStore.rm can call Store.inner.remove
- IStore handles Recasting The Inner object into the IStorage Trait that all storage types should implement
IStorage Trait will be how we interact with the inner data as a Trait Object. Once its a Trait object
we no longer have direct access to the data object, only threw whats in IStorage
IStorage<T:ICom> is the interface that all storages must implement.
- Since things get stored as Trait objects, program will not have direct access to the data. So every
path to the data needs to exist in this trait. From here any storage type can be used as long as it
implements this trait.
- This Trait is also the default type in ICom::Storage, which all components must implement and set
it's storage type.
ICom is the trait tha all components must implement
- One if the main purpose is to also contain the storage type. This way, the Main Data Struct will also have
the storage type with it, BUT even though you declare the real storage with the type, the default IStorage
trait is the only way to access the data unless you create and access the whole thing in one function.
**QUICK**
Components Must Impl ICom, Set storage type.
Storages.reg<T:ICom>, It creates a store from T::Storage and TypeId of T, saves in hash table
of <TypeId, Box<IStore>> but whats really Stored is Store<T>( RefCell<T::Storage> ).
Store also implements IStore, it can be saved as a Trait object in Box<IStore>
Store thats created is of Store< RefCell< T::Storage > >. Need to create store like Store::<T>::new() so the
struct knows how T and T::Storage.
When needing access to IStorage, need to get the Box<IStore> first, Then call on IStore.as_any() to recast
IStore as a dyn Any. With Any, you can call downcast_ref< Store<T> > to recast Any back to the
original store object. The store has internal mutability, so you need to perform a [Store].inner.borrow().
At which point, the borrowed object is your ICom::Storage as IStorage. From here, you can call any
method on IStorage to manage the data thats really stored in ICom::Storage (DenseVec<Pos>)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment