Remove entity-component system design (it ended up being types but worse)

stinkhead7ds
Ashley N. 2023-09-28 11:59:41 -04:00
parent 7a5bc41ce8
commit 27a6e09560
4 changed files with 85 additions and 173 deletions

View File

@ -1,6 +1,6 @@
use std::{error::Error, fs::File, io::Write, path::Path, collections::HashMap};
use clap::Parser;
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_ecs, get_sprites}}};
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_objs, get_sprites}}};
use super::settings::{Args, Tools, TileOutputFormat, TileOrder};
pub fn run_command() -> Result<(), Box<dyn Error>> {
@ -65,34 +65,10 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
print_good( "all files converted successfully" );
}
Tools::Level { input_file, output_directory, component_ids, attribute_ids, symbol_ids, sprite_ids, console: _ } => {
Tools::Level { input_file, output_directory, fields, symbol_ids, sprite_ids, console: _ } => {
// Clap can't do it. Sad!
let mut all_components: HashMap<String, u8> = HashMap::new();
let mut all_symbols: HashMap<String, u16> = HashMap::new();
let mut all_attributes: HashMap<String, Vec<String>> = HashMap::new();
let mut all_sprites: HashMap<String, u16> = HashMap::new();
for pair in component_ids {
let components: Vec<&str> = pair.split( "=" ).collect();
if components.len() != 2 {
return Err( format!( "invalid format for component_ids: {}", pair ) )?;
}
let component_name = components[ 0 ];
let component_id: u8 = components[ 1 ].parse()?;
all_components.insert( component_name.to_owned(), component_id );
}
for pair in attribute_ids {
let components: Vec<&str> = pair.split( "=" ).collect();
if components.len() != 2 {
return Err( format!( "invalid format for attribute_ids: {}", pair ) )?;
}
let component_name = components[ 0 ];
let attributes: Vec<String> = components[ 1 ].split( "," ).map( | string | string.to_owned() ).collect();
all_attributes.insert( component_name.to_owned(), attributes );
}
for pair in symbol_ids {
let symbols: Vec<&str> = pair.split( "=" ).collect();
if symbols.len() != 2 {
@ -116,10 +92,10 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
all_sprites.insert( symbol_name.to_owned(), symbol_id );
}
let component_ids = all_components;
let attribute_ids = all_attributes;
let symbol_ids = all_symbols;
let sprite_ids = all_sprites;
// TODO: will be used when custom object fields are implemented
let _fields: Vec<&str> = fields.split( "," ).collect();
let tiled_file = get_tiled_tilemap( &input_file )?;
@ -142,9 +118,9 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
nametables_bin.write_all( &get_collision_map( &tiled_file )? )?;
print_good( "exported collision.lvc" );
let mut nametables_bin = File::create( format!( "{}objects.ecs", output_directory ) )?;
nametables_bin.write_all( &get_ecs( &tiled_file, &component_ids, &attribute_ids, &symbol_ids )? )?;
print_good( "exported objects.ecs" );
let mut objs_bin = File::create( format!( "{}objects.obs", output_directory ) )?;
objs_bin.write_all( &get_objs( &tiled_file, &symbol_ids )? )?;
print_good( "exported objects.obs" );
let mut sprites_bin = File::create( format!( "{}sprites.spt", output_directory ) )?;
sprites_bin.write_all( &get_sprites( &tiled_file, &sprite_ids )? )?;

View File

@ -109,15 +109,11 @@ pub enum Tools {
#[arg(short, long, default_value_t=String::from("./"))]
output_directory: String,
/// Zero or more ECS component ID definitions (format: <component_name>=<u8>)
#[arg(long, num_args(0..))]
component_ids: Vec<String>,
/// Zero or more fields in order to define objects' struct (after object ID, position x, and position y) (format: <field1>,<field2>,<field3>)
#[arg(long, default_value_t=String::from(""))]
fields: String,
/// Zero or more ECS component attributes (format: <component_name>=<attribute_1>, <attribute_2>, ...)
#[arg(long, num_args(0..))]
attribute_ids: Vec<String>,
/// Zero or more ECS symbol ID definitions (format: <symbol_name>=<u8>)
/// Zero or more symbol ID definitions used for string identifiers in objects (format: <symbol_name>=<u8>)
#[arg(long, num_args(0..))]
symbol_ids: Vec<String>,

View File

@ -1,18 +1,24 @@
use std::{error::Error, fs::read_to_string, path::Path, borrow::Cow};
use image::{DynamicImage, GenericImageView};
use linked_hash_map::LinkedHashMap;
use roxmltree::Node;
use crate::reskit::{utility::print_warning, cli::settings::TileOrder};
use super::ecs;
#[derive(Debug)]
pub struct TiledTilemap {
pub tileset: Vec<TiledTileset>,
pub layers: Vec<Layer>,
pub ecs: Vec<ecs::Entity>,
pub objects: Vec<Object>,
pub width: usize,
pub height: usize
}
#[derive(Debug)]
pub struct Object {
pub id: String,
pub attributes: LinkedHashMap<String, String>
}
#[derive(Debug)]
pub struct SpriteMetadata {
pub id: String,
@ -201,6 +207,42 @@ fn get_tiles( tileset: Node, first_gid: usize, working_directory: &str ) -> Resu
Ok( TiledTileset { first_gid, image, palettes, tile_order, sprite_metadata } )
}
pub fn get_objs( node: &Node ) -> Result<Vec<Object>, Box<dyn Error>> {
let mut result = Vec::new();
let objects = node.descendants().filter( | node | node.tag_name() == "object".into() );
for object in objects {
let object_id = object.attribute( "id" ).ok_or( "invalid file: object in objectgroup has no id attribute" )?;
if let Some( properties ) = object.descendants().find( | node | node.tag_name() == "properties".into() ) {
if let Some( object_id_property ) = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-object-id" ) ) {
let id = object_id_property.attribute( "value" ).ok_or( "invalid file: property has no value attribute" )?.to_owned();
let mut attributes = LinkedHashMap::new();
// Get position x and y as beginning attributes
let ( x, y ) = (
object.attribute( "x" ).ok_or( "invalid file: object has no x position" )?.to_owned(),
object.attribute( "y" ).ok_or( "invalid file: object has no y position" )?.to_owned()
);
attributes.insert( "x".trim().to_owned(), x );
attributes.insert( "y".trim().to_owned(), y );
// TODO: Custom object fields
result.push( Object { id, attributes } );
} else {
print_warning( &format!( "object {} has no \"reskit-object-id\" property....ignoring this object. this is probably not what you want.", object_id ) );
}
} else {
print_warning( &format!( "object {} has no properties....ignoring this object. this is probably not what you want", object_id ) );
}
}
Ok( result )
}
pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
let file = read_to_string( path )?;
let document = roxmltree::Document::parse( &file )?;
@ -294,8 +336,8 @@ pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
// Get the entity-component system
let object_group = map.descendants().find( | node | node.tag_name() == "objectgroup".into() );
let ecs = if let Some( object_group ) = object_group {
ecs::get_ecs( object_group )?
let objects = if let Some( object_group ) = object_group {
get_objs( &object_group )?
} else {
Vec::new()
};
@ -304,7 +346,7 @@ pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
TiledTilemap {
tileset,
layers,
ecs,
objects,
width,
height
}

View File

@ -215,145 +215,43 @@ pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Er
Ok( result )
}
/**
* Get the entity-component system defined. If none is defined return an empty vec.
*/
pub fn get_ecs( tilemap: &TiledTilemap, component_ids: &HashMap<String, u8>, attribute_ids: &HashMap<String, Vec<String>>, symbol_ids: &HashMap<String, u16> ) -> Result<Vec<u8>, Box<dyn Error>> {
if tilemap.ecs.is_empty() {
return Ok( vec![] )
}
pub fn get_objs( tilemap: &TiledTilemap, symbol_ids: &HashMap<String, u16> ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut result: Vec<u8> = Vec::new();
// Build a complete set of types
let mut types: Vec<Vec<u8>> = Vec::new();
for entity in &tilemap.ecs {
// Get the list of component IDs attached to this entity
let mut components: Vec<u8> = entity.components.keys()
.map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) )
.collect::<Result<Vec<u8>, Box<dyn Error>>>()?;
components.sort();
// 2 bytes: Number of objects
result.extend( ( tilemap.objects.len() as u16 ).to_be_bytes() );
// Assign this unique combination of components a type id by inserting it into `types`
if let None = types.iter().find( | ecs_type | ecs_type == &&components ) {
types.push( components );
}
}
if tilemap.objects.len() > 0 {
let archetype = tilemap.objects.first().ok_or( "internal error: no first object" )?;
let largest_type_size: u16 = types.iter()
.map( | components | components.len() as u16 )
.reduce( | prev, current | max( prev, current ) )
.ok_or( "internal error: type is empty" )?;
// 2 bytes: Object struct size
result.extend( ( ( archetype.attributes.len() + 1 ) as u16 ).to_be_bytes() );
// Type Table
// 2 bytes: Number of Types
result.extend( ( types.len() as u16 ).to_be_bytes() );
// For (number of objects):
for object in &tilemap.objects {
// (Object struct size * 2): Data for object, ordered by --fields option, each one 16-bit field
// 2 bytes: Type Definition Union Size
result.extend( largest_type_size.to_be_bytes() );
// First output the object ID
let object_id = symbol_ids.get( &object.id ).ok_or( format!( "invalid file: undefined symbol \"{}\"", object.id ) )?;
result.extend( object_id.to_be_bytes() );
// For (Number of Types):
for ecs_type in &types {
// (Type Definition Union Size) bytes: List of Component IDs
let mut type_component_ids: Vec<u8> = ecs_type.clone();
// Fill the remainder of the union with 0xFF, if needed
let remainder = largest_type_size as usize - type_component_ids.len();
for _ in 0..remainder {
type_component_ids.push( 0xFF );
}
result.extend( type_component_ids );
}
// Object Table
// 2 bytes: Object Table Size
result.extend( ( 512 as u16 ).to_be_bytes() );
// For (Object Table Size)
for entity in &tilemap.ecs {
let mut components: Vec<u8> = entity.components.keys()
.map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) )
.collect::<Result<Vec<u8>, Box<dyn Error>>>()?;
components.sort();
// What type id is this?
let type_id: u8 = types.iter().position( | ecs_type | ecs_type == &components ).ok_or( "internal error: no type id" )? as u8;
// Output type id
result.push( type_id );
}
// Fill the remainder of the sparse Object Array with 0xFF, if needed
let remainder = 512 - tilemap.ecs.len();
for _ in 0..remainder {
result.push( 0xFF );
}
// Find the largest size of a combination of attributes (the component attribute union size)
let largest_type_attributes_size = tilemap.ecs.iter()
.map( | entity | {
let mut total_attributes_for_entity = 0;
for ( _, component ) in &entity.components {
total_attributes_for_entity += component.attributes.len()
}
total_attributes_for_entity
} )
.reduce( | prev, current | max( prev, current ) )
.ok_or( "internal error: no largest type attributes size" )? as u16;
// 2 bytes: Component Attribute Union Size
result.extend( largest_type_attributes_size.to_be_bytes() );
// For (Object Table Size):
for entity in &tilemap.ecs {
let mut written_attributes = 0;
let mut components: Vec<(&String, &Component)> = entity.components.iter().collect();
components.sort_by_key( | ( component_id, _ ) | component_ids[ *component_id ] );
for ( component_id, component ) in components {
let attributes_in_order = attribute_ids.get( component_id );
if let Some( attributes_in_order ) = attributes_in_order {
for attribute_id in attributes_in_order {
let attribute_value = component.attributes.get( attribute_id ).ok_or( format!( "invalid file: undefined attribute \"{}\" for component \"{}\"", attribute_id, component_id ) )?;
written_attributes += 1;
match attribute_value.as_str() {
"true" => result.extend( ( 1 as u16 ).to_be_bytes() ),
"false" => result.extend( ( 0 as u16 ).to_be_bytes() ),
string => {
let try_as_u16: Result<u16, ParseIntError> = string.parse();
if let Ok( numeric ) = try_as_u16 {
result.extend( numeric.to_be_bytes() );
} else {
let id: u16 = *symbol_ids.get( string ).ok_or( format!( "invalid file: undefined symbol \"{}\"", string ) )?;
result.extend( id.to_be_bytes() );
}
// Then output the attributes in the order provided via LinkedHashMap
for ( _attribute, value ) in &object.attributes {
let value: u16 = match value.as_str() {
"true" => 0x01,
"false" => 0x00,
all_else => {
if let Ok( valid_u16 ) = all_else.parse::<u16>() {
valid_u16
} else {
*symbol_ids.get( all_else ).ok_or( format!( "invalid file: undefined symbol \"{}\"", all_else ) )?
}
}
}
} else {
print_info( &format!( "component \"{}\" has no defined attributes (command line option --attribute-ids)", component_id ) );
};
result.extend( value.to_be_bytes() );
}
}
// Fill the remainder of the union with 0xFF, if needed
let remainder = largest_type_attributes_size - written_attributes;
for _ in 0..remainder {
result.extend( ( 0xFFFF as u16 ).to_be_bytes() );
}
}
// Fill the remainder of the sparse array with 0xFF, if needed
let remainder = 512 - tilemap.ecs.len();
for _ in 0..remainder {
for _ in 0..largest_type_attributes_size {
result.extend( ( 0xFFFF as u16 ).to_be_bytes() );
}
}
Ok( result )
@ -440,7 +338,7 @@ pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) -
incbin '{path_prefix}{level_name}/collision.lvc'
{level_label}Objects:
incbin '{path_prefix}{level_name}/objects.ecs'
incbin '{path_prefix}{level_name}/objects.obs'
{level_label}Sprites:
incbin '{path_prefix}{level_name}/sprites.spt'