Reputation: 2929
I'm building a rogue-like, I've already gotten a data loader working and part of the ECS working (building from scratch). The data is stored in .yml
files and is used to describe things in the game (in this instance mobs) and what features those things have, for example:
---
orc:
feature_packs:
- physical
- basic_identifiers_mob
features:
- component: char
initial_value: T
goblin:
feature_packs:
- physical
- basic_identifiers_mob
features:
- component: char
initial_value: t
As you can see, there are two mobs described, a goblin and an orc, they both possess two feature packs (groups of features) and also posses a char
feature that's used to describe what they look like to the player.
The initial_value
field can be a string, an integer, a floating point, a bool, a range, etc depending on what the component requires, this will indicate the value or possible values the component may have when the component is generated during entity building/creation.
The problem is that I don't know how to, when iterating over the features, select the struct based on the component's name, for example, select the Char
struct for the "char"
feature.
To better describe what I mean, I've written an example in a language I better understand, Ruby:
data_manager = function_that_loads_data('folder_path')
Entity_Manager.build(:mob, :orc, data_manager)
class Entity_Manager
class << self
attr_accessor :entities, :components
end
def self.build(entity_type, template_name, data_manager)
template = data_manager[entity_type][template_name]
entity_id = generate_unique_id
entities[entity_id] = Entity.new(entity_id, components: template.components.keys)
template.components.each do |component|
components[component.name][entity_id] =
Components.get(component.name).new(component.initial_value) # <= This part, how do I do the equivalent in rust, a function that will return or allow me to get or create a struct based on the value of a string variable
end
end
end
Now serde is the only thing I know that seems to be able to read text data and transform it into data, so to that end
How can I use serde (or a more appropriate non-serde using solution) to take the names of the feature and retrieve the correct struct, all implementing a type?
Incidentally, the one solution I'm trying not to use is a giant match statement.
The repo of my work as it stands is here
What I'm trying to avoid is doing somthing like this:
pub fn get(comp_name: &String) -> impl Component {
match comp_name.as_ref() {
"kind" => Kind,
"location" => Location,
"name" => Name,
"position" => Position,
"char" => Char,
}
}
because it's not really maintainable, though a macro would help a lot, I'm not very good at those atm and it doesn't even work, rust keeps thinking I'm trying to initialize the types when I just want to return one of several possible types that all will implement Component
EDIT: Becuase it looks like I'm not clear enough:
pub enum InitialValue {
Char(char),
String(String),
Int(i32),
Float(f32),
Bool(bool),
Range(Range<i32>),
Point((i32,i32))
}
impl InitialValue {
pub fn unwrap_char(&self) -> &char {
match &self {
InitialValue::Char(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_string(&self) -> &String {
match &self {
InitialValue::String(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_int(&self) -> &i32 {
match &self {
InitialValue::Int(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_float(&self) -> &f32 {
match &self {
InitialValue::Float(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_bool(&self) -> &bool {
match &self {
InitialValue::Bool(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_range(&self) -> &Range<i32> {
match &self {
InitialValue::Range(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
pub fn unwrap_point(&self) -> &(i32, i32) {
match &self {
InitialValue::Point(val) => val,
_ => panic!("Stored value does not match unwrap type")
}
}
}
#[derive(Debug, Deserialize)]
pub struct Component {
#[serde(rename="component")]
name: String,
#[serde(default)]
initial_value: Option<InitialValue>,
}
#[derive(Debug, Deserialize)]
pub struct Template {
pub feature_packs: Vec<String>,
pub features: Vec<Component>,
}
How do I transform the templates into instances of entities?
Specifcally, How do I for a given Component.name
find the component
and then initialize it? OR is my aproach wrong and there's a better
way.
Upvotes: 1
Views: 1996
Reputation: 15877
Sounds like you want a tagged union, or sum type; Rust knows these as enum
erations. Serde even supports using container internal tags. So here's my little experiment:
#[macro_use] extern crate serde_derive;
extern crate serde_yaml;
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag="component")]
enum Feature {
Char { initial_value : char },
Weight { kgs : u32 }
}
fn main() {
let v = vec![
Feature::Char{initial_value:'x'},
Feature::Weight{kgs:12}
];
println!("{}", serde_yaml::to_string(&v).unwrap());
}
This outputs:
---
- component: Char
initial_value: x
- component: Weight
kgs: 12
Probably the next step is to make dedicated structs for the variants.
Upvotes: 1