Skip to content

Instantly share code, notes, and snippets.

@arthurmco
Last active April 23, 2020 00:59
Show Gist options
  • Save arthurmco/92ab10eb55cbc332132010bb5b46502a to your computer and use it in GitHub Desktop.
Save arthurmco/92ab10eb55cbc332132010bb5b46502a to your computer and use it in GitHub Desktop.
Core structures of Familyline logic engine in Rust
use std::sync::Arc;
/// Familyline test for a possible Rust rewrite
#[derive(Debug, Copy, Clone)]
pub struct Vec3 {
x: f32,
y: f32,
z: f32,
}
/// A dummy mesh object
#[derive(Debug)]
pub struct MeshObject {}
/// Stores information about who represents this entity
/// on screen
pub trait LocationComponentT: core::fmt::Debug {
fn get_mesh(&self) -> Option<&MeshObject>;
fn get_mesh_mut(&mut self) -> Option<&mut MeshObject>;
fn update_mesh(&mut self, m: MeshObject);
}
/// Attack information table
///
/// Essentially, the HP lost is the attacker's attack minus the defender armor.
#[derive(Debug, Clone)]
pub struct AttackInformation {
/// Amount of damage dealt, per tick
attack: f32,
/// Amount of armor blocked, per tick
armor: f32,
}
/// Why one object can't attack the other
pub enum CanAttackFailReason {
/// The defender is out of range
OutOfRange,
/// The defender cannot be attacked because it does not have an
/// attack component.
NotAttackable,
// Both entities are from the same colony
SameColonies,
// Both entities come from allied colonies
AlliedColonies,
}
/// Stores information about how two entities will attack
///
/// If an entity misses this component, it means that the entity
/// will not be able to attack, but will not be able to be attacked
/// either.
pub trait AttackComponentT: core::fmt::Debug {
fn get_total_hp(&self) -> u32;
/// Get current HP
///
/// Floating point to allow any attack that removes less than 1HP to be
/// registered and accounted for
fn get_current_hp(&self) -> f64;
fn get_attack_information(&self) -> &AttackInformation;
/// Check if an unit can attack another
fn can_attack(&self, defender: &dyn GameEntity) -> Result<(), CanAttackFailReason>;
}
/// Game entity main trait
///
/// All entities should implements the methods below
///
/// We implement `Debug` just so that we can see what is inside.
pub trait GameEntity: core::fmt::Debug {
fn get_id(&self) -> Option<usize>;
fn update_id(&mut self, id: usize);
fn get_position(&self) -> Option<Vec3>;
fn set_position(&mut self, v: Vec3);
fn get_location_component(&self) -> Option<&dyn LocationComponentT>;
fn get_location_component_mut(&mut self) -> Option<&mut dyn LocationComponentT>;
fn get_attack_component(&self) -> Option<&dyn AttackComponentT>;
fn get_attack_component_mut(&mut self) -> Option<&mut dyn AttackComponentT>;
fn get_name(&self) -> &str;
fn get_type(&self) -> &str;
fn update(&mut self) {}
}
/////////
#[derive(Debug)]
struct DefaultLocationComponent {
m: Option<MeshObject>,
}
impl DefaultLocationComponent {
fn new() -> DefaultLocationComponent {
DefaultLocationComponent { m: None }
}
}
impl LocationComponentT for DefaultLocationComponent {
fn get_mesh(&self) -> Option<&MeshObject> {
match &self.m {
Some(m) => Some(m),
None => None,
}
}
fn get_mesh_mut(&mut self) -> Option<&mut MeshObject> {
match &mut self.m {
Some(m) => Some(m),
None => None,
}
}
fn update_mesh(&mut self, m: MeshObject) {
self.m = Some(m)
}
}
//////////////
#[derive(Debug)]
struct DefaultAttackComponent {
info: AttackInformation,
total_hp: u32,
current_hp: f64,
}
impl DefaultAttackComponent {
fn new(hp: u32, info: AttackInformation) -> DefaultAttackComponent {
DefaultAttackComponent {
total_hp: hp,
current_hp: hp as f64,
info,
}
}
}
impl AttackComponentT for DefaultAttackComponent {
fn get_total_hp(&self) -> u32 {
self.total_hp
}
fn get_current_hp(&self) -> f64 {
self.current_hp
}
fn get_attack_information(&self) -> &AttackInformation {
&self.info
}
fn can_attack(&self, defender: &dyn GameEntity) -> Result<(), CanAttackFailReason> {
Err(CanAttackFailReason::NotAttackable)
}
}
///////////////////////
/// The entity manager
///
/// Stores, and has absolute ownership of all entities.
#[derive(Debug)]
struct EntityManager {
next_id: usize,
entities: Vec<Box<dyn GameEntity>>,
}
impl EntityManager {
fn new() -> EntityManager {
EntityManager {
next_id: 1,
entities: Vec::new(),
}
}
fn count(&self) -> usize {
self.entities.len()
}
/// Add an object, return its ID
///
/// We store the ownership to the game entity.
fn add(&mut self, mut v: Box<dyn GameEntity>) -> usize {
let id = self.next_id;
v.update_id(id);
self.entities.push(v);
self.next_id = id + 1;
id
}
/// Get a reference from an object from its ID
fn get(&self, id: usize) -> Option<&Box<dyn GameEntity>> {
self.entities.iter().find(|c| {
if let Some(oid) = c.get_id() {
oid == id
} else {
false
}
})
}
/// Get a mutable reference from an object from its ID
fn get_mut(&mut self, id: usize) -> Option<&mut Box<dyn GameEntity>> {
self.entities.iter_mut().find(|c| {
if let Some(oid) = c.get_id() {
oid == id
} else {
false
}
})
}
/// Remove an object with the ID `id` from the object manager
///
/// You should already have notified the entity lifecycle manager
/// about the removal of this component.
///
/// We unwrap the ID because the object should have an ID already
fn remove(&mut self, id: usize) {
self.entities.retain(|e| e.get_id().unwrap() != id)
}
fn update(&mut self) {
for e in &mut self.entities {
e.update();
}
}
}
/////////////////////////////
/// A generic entity, with no customization besides the update callback
///
/// Useful for 99% of the cases.
struct GenericEntity<Cb>
where
Cb: Fn(&mut dyn GameEntity) -> (),
{
id: Option<usize>,
name: String,
entity_type: String,
position: Option<Vec3>,
lc: DefaultLocationComponent,
ac: DefaultAttackComponent,
/// The update callback
///
/// We use an Arc<> so we can copy the callback from the struct,
/// and call it, without having to borrow it from the generic entity
/// structure
///
/// Since we pass the `self` object to the callback, so it can do
/// alterations, it would not compile, because we would borrow it
/// twice.
update_callback: Arc<Box<Cb>>,
}
impl<Cb> GenericEntity<Cb>
where
Cb: Fn(&mut dyn GameEntity) -> (),
{
fn new(name: &str, entity_type: &str, update_callback: Cb) -> GenericEntity<Cb> {
GenericEntity {
id: None,
name: String::from(name),
entity_type: String::from(entity_type),
position: None,
lc: DefaultLocationComponent::new(),
ac: DefaultAttackComponent::new(
100,
AttackInformation {
attack: 1.0,
armor: 0.8,
},
),
update_callback: Arc::new(Box::new(update_callback)),
}
}
}
impl<Cb> GameEntity for GenericEntity<Cb>
where
Cb: Fn(&mut dyn GameEntity) -> (),
{
fn get_id(&self) -> Option<usize> {
self.id
}
fn get_name(&self) -> &str {
&self.name
}
fn update_id(&mut self, id: usize) {
self.id = Some(id);
}
fn get_position(&self) -> Option<Vec3> {
self.position
}
fn set_position(&mut self, v: Vec3) {
self.position = Some(v);
}
fn get_location_component(&self) -> Option<&dyn LocationComponentT> {
Some(&self.lc)
}
fn get_location_component_mut(&mut self) -> Option<&mut dyn LocationComponentT> {
Some(&mut self.lc)
}
fn get_attack_component(&self) -> Option<&dyn AttackComponentT> {
Some(&self.ac)
}
fn get_attack_component_mut(&mut self) -> Option<&mut dyn AttackComponentT> {
Some(&mut self.ac)
}
fn get_type(&self) -> &str {
&self.entity_type
}
fn update(&mut self) {
(self.update_callback.clone())(self);
println!("Updated");
}
}
use std::fmt;
/// Manually implement the Debug trait, because `Fn`s do not
/// have a debug trait implementation.
/// We only have to add something generic here
impl<Cb> fmt::Debug for GenericEntity<Cb>
where
Cb: Fn(&mut dyn GameEntity) -> (),
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GenericEntity")
.field("id", &self.id)
.field("name", &self.name)
.field("position", &self.position)
.field("lc", &self.lc)
.field("ac", &self.ac)
.field("update_callback", &String::from("Fn<>"))
.finish()
}
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
#[test]
fn test_object_add() {
let mut em = EntityManager::new();
let de = GenericEntity::new("Test1", "test", |_| ());
let id = em.add(Box::new(de));
assert_eq!(id, 1);
let de = GenericEntity::new("Test2", "test", |_| ());
let id = em.add(Box::new(de));
assert_eq!(id, 2);
assert_eq!(em.count(), 2);
}
#[test]
fn test_object_add_and_get() -> Result<(), String> {
let mut em = EntityManager::new();
let _ = em.add(Box::new(GenericEntity::new("Invalid", "test", |_| ())));
let id = em.add(Box::new(GenericEntity::new("Test0001", "test", |_| ())));
let _ = em.add(Box::new(GenericEntity::new(
"AnotherInvalid",
"test",
|_| (),
)));
let entity = em.get(id);
match entity {
Some(e) => {
assert_eq!(e.get_id().unwrap(), id);
assert_eq!(e.get_name(), "Test0001");
Ok(())
}
None => Err(String::from("the entity should be found, it was not found")),
}
}
#[test]
fn test_object_call_update() -> Result<(), String> {
let mut em = EntityManager::new();
let _ = em.add(Box::new(GenericEntity::new("Invalid", "test", |_| ())));
let id = em.add(Box::new(GenericEntity::new(
"TestCallback",
"test",
|mut e| {
e.set_position(Vec3 {
x: 10.0,
y: 1.0,
z: 5.0,
});
},
)));
let _ = em.add(Box::new(GenericEntity::new(
"AnotherInvalid",
"test",
|_| (),
)));
em.update();
let entity = em.get(id).unwrap();
match entity.get_position() {
Some(pos) => {
assert_eq!(pos.x, 10.0);
assert_eq!(pos.y, 1.0);
assert_eq!(pos.z, 5.0);
Ok(())
}
None => Err(String::from(
"the position should be set from the closure!!!",
)),
}
}
#[test]
fn test_object_call_update_multiple_times() {
let mut em = EntityManager::new();
let _ = em.add(Box::new(GenericEntity::new("Invalid", "test", |_| ())));
let id = em.add(Box::new(GenericEntity::new(
"TestCallbackMulti",
"test",
|mut e| {
let pos = e.get_position();
let delta = Vec3 {
x: 10.0,
y: 0.0,
z: 5.0,
};
e.set_position(match pos {
None => Vec3 {
x: 10.0,
y: 1.0,
z: 5.0,
},
Some(v) => Vec3 {
x: v.x + delta.x,
y: v.y + delta.y,
z: v.z + delta.z,
},
});
},
)));
let _ = em.add(Box::new(GenericEntity::new(
"AnotherInvalid",
"test",
|_| (),
)));
em.update();
em.update();
em.update();
em.update();
em.update();
let entity = em.get(id).unwrap();
let pos = entity.get_position().unwrap();
assert_eq!(pos.x, 50.0);
assert_eq!(pos.y, 1.0);
assert_eq!(pos.z, 25.0);
}
#[test]
fn test_object_delete() -> Result<(), String> {
let mut em = EntityManager::new();
let _ = em.add(Box::new(GenericEntity::new("Invalid", "test", |_| ())));
let id = em.add(Box::new(GenericEntity::new("TestDelete", "test", |_| ())));
let _ = em.add(Box::new(GenericEntity::new(
"AnotherInvalid",
"test",
|_| (),
)));
assert_eq!(em.count(), 3);
em.remove(id);
assert_eq!(em.count(), 2);
match em.get(id) {
Some(_) => Err(String::from("this object should not exist now")),
None => Ok(()),
}
}
}
fn main() {
println!("Hello, world!");
let mut em = EntityManager::new();
let mut de = GenericEntity::new("Test1", "test", |_| ());
de.get_location_component_mut()
.unwrap()
.update_mesh(MeshObject {});
println!("{:?}", em);
println!("{:?}", de);
let id = em.add(Box::new(de));
println!("{}", id);
println!("{:?}", em);
let mut de2 = GenericEntity::new("Test2", "test", |_| ());
let id = em.add(Box::new(de2));
em.get_mut(1).unwrap().set_position(Vec3 {
x: 10.0,
y: 1.0,
z: 10.0,
});
println!("{}", id);
println!("{:?}", em);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment