Skip to content

Instantly share code, notes, and snippets.

@mickvangelderen
Created November 17, 2017 23:30
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 mickvangelderen/09dcbc866bd7f014690bff36dd8a99ab to your computer and use it in GitHub Desktop.
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.
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