Created
November 17, 2017 23:30
-
-
Save mickvangelderen/09dcbc866bd7f014690bff36dd8a99ab to your computer and use it in GitHub Desktop.
An exploration of implementing accessors in rust so that changes in struct layout/composition do not require changes in the code using them.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate gl; | |
extern crate cgmath; | |
use gl::types::*; | |
use cgmath::*; | |
macro_rules! def_accessor_trait { | |
(($Trait:ident, $method:ident), ($TraitMut:ident, $method_mut:ident), $Field:ty) => { | |
pub trait $Trait { | |
fn $method(&self) -> &$Field; | |
} | |
pub trait $TraitMut { | |
fn $method_mut(&mut self) -> &mut $Field; | |
} | |
} | |
} | |
macro_rules! impl_accessor_trait { | |
( | |
($Trait:ty, $method:ident), | |
($TraitMut:ty, $method_mut:ident), | |
$Src:ty, $($field:ident).*: $Field:ty | |
) => { | |
impl $Trait for $Src { | |
fn $method(&self) -> &$Field { | |
&self.$($field).* | |
} | |
} | |
impl $TraitMut for $Src { | |
fn $method_mut(&mut self) -> &mut $Field { | |
&mut self.$($field).* | |
} | |
} | |
} | |
} | |
def_accessor_trait!{ | |
(HasPosition, position), | |
(HasPositionMut, position_mut), | |
Vector3<GLfloat> | |
} | |
macro_rules! impl_has_position { | |
($T:ty, $($field:ident).*) => { | |
impl_accessor_trait! { | |
(HasPosition, position), | |
(HasPositionMut, position_mut), | |
$T, $($field).*: Vector3<GLfloat> | |
} | |
} | |
} | |
def_accessor_trait!{ | |
(HasOrientation, orientation), | |
(HasOrientationMut, orientation_mut), | |
Quaternion<GLfloat> | |
} | |
macro_rules! impl_has_orientation { | |
($T:ty, $($field:ident).*) => { | |
impl_accessor_trait! { | |
(HasOrientation, orientation), | |
(HasOrientationMut, orientation_mut), | |
$T, $($field).*: Quaternion<GLfloat> | |
} | |
} | |
} | |
// Assume this data is accessed frequently by some rendering/collision detection code. | |
pub struct EntityHot { | |
position: Vector3<GLfloat>, | |
orientation: Quaternion<GLfloat>, | |
} | |
impl EntityHot { | |
pub fn new(position: Vector3<GLfloat>, orientation: Quaternion<GLfloat>) -> EntityHot { | |
EntityHot { | |
position, | |
orientation, | |
} | |
} | |
} | |
impl_has_position!{ EntityHot, position } | |
impl_has_orientation!{ EntityHot, orientation } | |
// Assume this data is not accessed that frequently but is shared by all entities. | |
pub struct EntityCold { | |
pub name: String, | |
} | |
// An example entity which defines some additional data. | |
pub struct Door { | |
entity_hot: Box<EntityHot>, | |
entity_cold: Box<EntityCold>, | |
pub open: bool, | |
} | |
impl Door { | |
pub fn new( | |
position: Vector3<GLfloat>, | |
orientation: Quaternion<GLfloat>, | |
name: String, | |
open: bool, | |
) -> Self { | |
Door { | |
entity_hot: Box::new(EntityHot::new(position, orientation)), | |
entity_cold: Box::new(EntityCold { name }), | |
open, | |
} | |
} | |
} | |
impl_has_position!{ Door, entity_hot.position } | |
impl_has_orientation!{ Door, entity_hot.orientation } | |
// Now imagine that we want to move fields between EntityHot and | |
// EntityCold too see if doing so increases performance. We don't have | |
// to change all our code using the Door struct because the fields are | |
// accessed through the accessor methods defined by the accessor traits. | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
fn reset_position<T: HasPositionMut>(thing: &mut T) { | |
*thing.position_mut() = Vector3::zero(); | |
} | |
#[test] | |
fn door() { | |
let mut d = Door::new( | |
Vector3::new(1.0, 2.0, 3.0), | |
Quaternion::one(), | |
String::from("Major Doormo"), | |
false, | |
); | |
assert_eq!(d.position(), &Vector3::new(1.0, 2.0, 3.0)); | |
reset_position(&mut d); | |
assert_eq!(d.position(), &Vector3::zero()); | |
*d.position_mut() = Vector3::new(3.0, 2.0, 1.0); | |
assert_eq!(d.position(), &Vector3::new(3.0, 2.0, 1.0)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment