1

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

  • Data manager - Loads and manages data loaded into the game
  • Entity manager - Manages entities and there components (doesn't support bit keys atm)
  • Entity Builder - Where Entity's will be built using data from the data manager (this is where I'm currently stuck)
  • Components - a list of simple components

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:

  • I'm not trying to load gameplay objects into the game, I'm loading templates
  • I'm using those templates to then generate the entities that will be exist during gameplay
  • I can already load the data I want into the game in the following structure:
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.

  • And if I am doing it wrong, How do other games load data in and then use it to generate in game entities?
3
  • That's an interesting topic! Please show us what you have done so far. Serde is pretty easy to use. For easy things you just have to annotate your struct with #[derive(Serialize, Deserialize)] to get things working. What exactly is your problem? Commented Apr 5, 2019 at 13:56
  • 1
    You should share minimal reproducible example in Rust - the language you have problem with, not Ruby. Please, share your code and show us what problem you have. Hard to guess, but you probably want to use one of the Enum representations. Also please check How to Ask (rust tag info). Commented Apr 5, 2019 at 14:24
  • I'm not trying to load in the mobs themselves, only templates which are used to construct more of them, loading the data in isn't the problem, constructing the entities (mobs) is the problem, I don't know how to select the components from the loaded data like in the example. This would be easy in ruby as I can just dynamically map the component name to it's corresponding class, but you can't do that in rust as far as I know. Commented Apr 5, 2019 at 14:29

1 Answer 1

1

Sounds like you want a tagged union, or sum type; Rust knows these as enumerations. 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.

Sign up to request clarification or add additional context in comments.

6 Comments

The question appears to include how to build this enum type from all the structs that implement Component. That's macro magic I don't know.
I'm grateful that you're trying to help I am, I've been stuck with this issue for months; but I need to stress, the data has already been loaded, it's already in the program, I'm trying to use already loaded data to generate new mobs using the loaded data, as templates not, load the data from the file each time I need to generate a mob
Why should your templates not be prototypes that you simply clone?
Because I wante to keep loading the data, the templates and in-game data separate from one another. I don't deny my approach might be wrong, I'm still new at this, If Trying to do it the way I am is wrong then It's wrong, and point me in the proper direction, I've asked how to do this properly many times but no one has pointed me in the right direction, offered me incorrect advice or just not been helpful at at all.
It might not be wrong, but it sure seems trickier. Essentially the field-naming introspection is done by things like the serde deriving macros, and you'd be doing it another time to create a distinct layer of runtime introspection for those fields. I apologize, but I don't have the energy to map this out now.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.