-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Model reference architecture & Cohesion between Rust and Python #12
base: master
Are you sure you want to change the base?
Conversation
Added functionality to pass arguments to sim setup tools (python scripts)Now, when using the "sim run {scenario}" command, any arguments passed after the scenario will be made available to the python scripts that set up every scenario. They can then be processed to determine which models to activate, which inputs to set, etc. The limitation is your imagination! |
Initial cut of model reference architectureModel references can be made and managed with the ReferenceList. This is a struct that contains a vector of int "ids" and a vector of weak model pointers. (All models should use weak pointers to each other to keep the sim from leaking memory in the case of circular dependencies). The IDs are filled out by the python input infrastructure, and then the first step in the runtime after creating all the models is going into those references and filling out their pointer list with the pointers that correspond to the IDs already there. Model references can either be explicitly passed in: # Create models here
force = ForceEffector( 0, 10, 20, ModelBase( 100, 200 ) )
eom = EOM(
x=0,
y=0,
z=0,
force_effectors=ReferenceList( force ),
base=ModelBase( 100, 200 )
) or they can be grouped together. All models that are grouped together have references to each other - independent of their type. All of this is handled on the python input side. ModelGroup( eom, force ) These group references, or "local_refs," also do not need to be manually handled on the Rust side. Instead, it is all automatically taken care of by the runtime: pub trait SimModelTrait: Debug {
fn resolve_references( &mut self, global_model_list: &HashMap<ModelID, ModelPtr> ) {
let locals = &mut self.get_details().local_refs;
for id in &locals.reference_ids {
// println!( "{}", id );
locals.reference_list.push(
Rc::downgrade( &global_model_list.get( id ).unwrap() )
)
}
}
} Models like EOM can then choose to use their own ForceEffector lists, the local list (if in a group), or even (preferred) move ForceEffectors from the local list into the ForceEffector list. Unfortunately as it is, using these references is extremely verbose: for reference in &self.base.local_refs.reference_list {
let upgraded = reference.upgrade().unwrap();
let mut contents = upgraded.deref().borrow_mut();
if let Some( force_model ) = contents.as_any().downcast_mut::<ForceEffector>() {
println!( " - Force ID: {}", force_model.get_details().id );
force_model.get_details().id = 5;
println!( " - Force ID (new): {}", force_model.get_details().id );
}
} My focus now will be creating a more streamlined way to create and access these references. |
In trying to bring a lot of the boilerplate into the class itself, I have determined that the ReferenceList architecture has some cumbersome limitations, namely that it is impossible to go from SimModelTrait RefCells to Sim Model RefCells. It appears as though I might have to make my own kind of RefCell that has this functionality baked in. |
After spending a long time investigating my previous report, I have found an alternative way this could work - Enums. Here is a minimal-reproducible example. use std::{rc::{Rc, Weak}, cell::RefCell, borrow::Borrow};
trait SimModelTrait
{
fn doSomething(&self);
}
/////////////////////////////////// POINT
struct Point
{
x: f64,
y: f64,
}
impl SimModelTrait for Point
{
fn doSomething(&self) {}
}
impl FromModel for Point {
fn from_model_mutref(model: &mut Models) -> &mut Self {
match model {
Models::Point( point ) => point,
_ => panic!("Tried")
}
}
}
///////////////////////////////////// Models
enum Models
{
Point( Point )
}
impl Models {
fn downcast_mut<T: FromModel>( &mut self ) -> &mut T {
FromModel::from_model_mutref( self )
}
}
trait FromModel {
fn from_model_mutref( model: &mut Models ) -> &mut Self;
}
fn main()
{
let mut point = Models::Point( Point { x: 0.0, y: 1.01 } );
let concrete = point.downcast_mut::<Point>();
concrete.y = 1000000.0;
let concrete2 = point.downcast_mut::<Point>();
concrete2.x = 1992.0;
if let Models::Point( p ) = point {
println!("x: {}, y: {}", &p.x, &p.y);
}
} In this example, we can call "downcast_mut" on the Models enum to turn it into a specific type, or panic. This could also be handled with an Ok(T) or Err(()), to prevent crashes. As shown, it is clear that we can have multiple mutable references to the actual Model enum entity. This is the behavior I am going for, but I am unsure how safe it is to use with multiple threads. I very much want to implement it in a way that we can use a refcell to enforce borrowing. Further investigation will look into that. |
This pull request contains the following improvements: