1 /** 2 * Defines the IComponent interface, which is the base for all components. 3 */ 4 module dash.components.component; 5 import dash.core, dash.components, dash.graphics, dash.utility; 6 7 import vibe.data.bson, vibe.data.json, dash.utility.data.yaml; 8 import std.algorithm, std.array, std..string, std.traits, std.conv, std.typecons; 9 10 /// Tests if a type can be created from yaml. 11 enum isComponent(alias T) = is( T == class ) && is( T : Component ) && !__traits( isAbstractClass, T ); 12 private enum perSerializationFormat( string code ) = "".reduce!( ( working, type ) => working ~ code.replace( "$type", type ) )( serializationFormats ); 13 alias helper( alias T ) = T; 14 alias helper( T... ) = T; 15 alias helper() = TypeTuple!(); 16 17 template isSerializableField( alias field ) 18 { 19 import vibe.internal.meta.uda; 20 21 public: 22 enum isSerializableField = 23 !isSomeFunction!field && 24 isMutable!fieldType && 25 !findFirstUDA!( IgnoreAttribute, field ).found; 26 27 private: 28 alias fieldType = typeof(field); 29 } 30 31 /** 32 * Interface for components to implement. 33 */ 34 abstract class Component 35 { 36 private: 37 /// The description from the last creation/refresh. 38 @ignore 39 Description lastDesc; 40 41 public: 42 /// The GameObject that owns this component. 43 @ignore 44 GameObject owner; 45 46 /// The function called on initialization of the object. 47 void initialize() { } 48 /// Called on the update cycle. 49 void update() { } 50 /// Called on the draw cycle. 51 void draw() { } 52 /// Called on shutdown. 53 void shutdown() { } 54 55 /** 56 * Create a description from the this parameter. 57 */ 58 final const(Description) description() @property 59 in 60 { 61 assert( getDescription( typeid(this) ), "No description found for type: " ~ typeid(this).name ); 62 } 63 body 64 { 65 return getDescription( typeid(this) ).fromComponent( this ); 66 } 67 68 /** 69 * Refresh an object with a new description 70 */ 71 final void refresh( Description desc ) 72 { 73 lastDesc.refresh( this, desc ); 74 } 75 76 /// For easy external access 77 alias Description = .Description; 78 } 79 80 /** 81 * A self-registering component. 82 * Useful for when you receive circular dependency errors. 83 * Recommended for use only when extending Component directly. 84 * 85 * Params: 86 * BaseType = The type being registered. 87 * 88 * Examples: 89 * --- 90 * class MyComponent : ComponentReg!MyComponent 91 * { 92 * // ... 93 * } 94 * --- 95 */ 96 abstract class ComponentReg( BaseType ) : Component 97 { 98 static this() 99 { 100 componentMetadata!(Unqual!(BaseType)).register(); 101 } 102 } 103 104 /// A map of all registered Component types to their descriptions 105 private immutable(Description)[ClassInfo] descriptionsByClassInfo; 106 private immutable(Description)[string] descriptionsByName; 107 108 immutable(Description) getDescription( ClassInfo type ) 109 { 110 return descriptionsByClassInfo.get( type, null ); 111 } 112 113 immutable(Description) getDescription( string name ) 114 { 115 return descriptionsByName.get( name, null ); 116 } 117 118 /// The description for the component 119 abstract class Description 120 { 121 public: 122 static struct Field 123 { 124 public: 125 string name; 126 string typeName; 127 string attributes; 128 string mod; 129 string serializer; 130 } 131 132 /// The type of the component. 133 @rename( "Type" ) 134 string type; 135 136 /// Get a list of teh fields on a component. 137 @ignore 138 abstract immutable(Field[]) fields() @property const; 139 140 /// Create an instance of the component the description is for. 141 abstract Component createInstance() const; 142 143 /// Refresh a component by comparing descriptions. 144 abstract void refresh( Component comp, const Description newDesc ); 145 146 /// Creates a description from a component. 147 abstract const(Description) fromComponent( const Component comp ) const; 148 149 /// Get the type of the component the description is for. 150 @ignore 151 abstract ClassInfo componentType() @property const; 152 153 /// Serializers and deserializers 154 mixin( perSerializationFormat!q{ 155 // Overridable serializers 156 abstract $type serializeDescriptionTo$type( Description c ) const; 157 abstract Description deserializeFrom$type( $type node ) const; 158 159 // Serializers for vibe 160 final $type to$type() const 161 { 162 if( auto desc = getDescription( componentType ) ) 163 { 164 return desc.serializeDescriptionTo$type( cast()this ); 165 } 166 else 167 { 168 errorf( "Description of type %s not found! Did you forget a mixin(registerComponents!())?", typeid(this).name ); 169 return $type(); 170 } 171 } 172 static Description from$type( $type data ) 173 { 174 // If it's Bson, convert it to a json object. 175 static if( is( $type == Bson ) ) 176 auto d = data.toJson(); 177 else 178 auto d = data; 179 180 if( auto type = "Type" in d ) 181 { 182 if( auto desc = getDescription( type.get!string ) ) 183 { 184 return desc.deserializeFrom$type( data ); 185 } 186 else 187 { 188 warningf( "Component's \"Type\" not found: %s", type.get!string ); 189 return null; 190 } 191 } 192 else 193 { 194 warningf( "Component doesn't have \"Type\" field." ); 195 return null; 196 } 197 } 198 static assert( is$typeSerializable!Description ); 199 } ); 200 } 201 202 /** 203 * To be placed at the top of any module defining YamlComponents. 204 * 205 * Params: 206 * modName = The name of the module to register. 207 */ 208 enum registerComponents( string modName = __MODULE__ ) = q{ 209 static this() 210 { 211 // Declarations 212 import mod = $modName; 213 214 // Foreach definition in the module (classes, structs, functions, etc.) 215 foreach( memberName; __traits( allMembers, mod ) ) static if( __traits( compiles, __traits( getMember, mod, memberName ) ) ) 216 { 217 // Alais to the member 218 alias member = helper!( __traits( getMember, mod, memberName ) ); 219 220 // If member is a class that extends Componen 221 static if( isComponent!member ) 222 { 223 componentMetadata!( member ).register(); 224 } 225 } 226 } 227 }.replace( "$modName", modName ); 228 229 /// Registers a type as a component 230 template componentMetadata( T ) if( isComponent!T ) 231 { 232 public: 233 /// Runtime function, registers serializers. 234 void register() 235 { 236 immutable desc = new immutable SerializationDescription; 237 descriptionsByClassInfo[ typeid(T) ] = desc; 238 descriptionsByName[ name ] = desc; 239 } 240 241 /// A list of fields on the component. 242 enum fieldList = getFields(); 243 244 /// The size of an instance of the component. 245 enum instanceSize = __traits( classInstanceSize, T ); 246 247 /// The name of the component. 248 enum name = __traits( identifier, T ); 249 250 private: 251 /** 252 * Generate actual description of a component. 253 * 254 * Contains: 255 * * A represenetation of each field on the component at root level. 256 * * A list of the fields ($(D fields)). 257 * * A method to create a description from an instance of a component ($(D fromComponent)). 258 * * A method to refresh an instance of the component with a newer description ($(D refresh)). 259 * * A method to create an instance of the component ($(D createInstance)). 260 * * The ClassInfo of the component ($(D componentType)). 261 */ 262 final class SerializationDescription : Description 263 { 264 mixin( { return reduce!( ( working, field ) { 265 // Append required import for variable type 266 if( field.mod ) working ~= "import " ~ field.mod ~ ";\n"; 267 // Append variable attributes 268 if( field.attributes ) working ~= "@(" ~ field.attributes ~ ")\n"; 269 // Append variable declaration 270 return working ~ field.typeName ~ " " ~ field.name ~ ";\n"; 271 } )( "", fieldList ); } () ); 272 273 // Generate serializers for the type 274 mixin( perSerializationFormat!q{ 275 override $type serializeDescriptionTo$type( Description desc ) const 276 { 277 return serializeTo$type( cast(SerializationDescription)desc ); 278 } 279 override SerializationDescription deserializeFrom$type( $type node ) const 280 { 281 return deserialize$type!SerializationDescription( node ); 282 } 283 } ); 284 285 /// Get a list of field descriptions 286 override immutable(Description.Field[]) fields() const @property 287 { 288 return fieldList; 289 } 290 291 /// Create a description from a component. 292 override const(SerializationDescription) fromComponent( const Component comp ) const 293 { 294 auto theThing = cast(T)comp; 295 auto desc = new SerializationDescription; 296 desc.type = name; 297 298 foreach( fieldName; __traits( allMembers, T ) ) 299 { 300 enum idx = fieldList.map!(f => f.name).countUntil( fieldName ); 301 static if( idx >= 0 ) 302 { 303 enum field = fieldList[ idx ]; 304 // Serialize the value for the description 305 mixin( "auto ser = "~field.serializer~".serialize(theThing."~field.name~");" ); 306 // Copy the value to the description 307 mixin( "desc."~field.name~" = ser;" ); 308 } 309 } 310 311 return desc; 312 } 313 314 /// Refresh a component by comparing descriptions. 315 override void refresh( Component comp, const Description desc ) 316 in 317 { 318 assert( cast(T)comp, "Component of the wrong type passed to the wrong description." ); 319 assert( cast(SerializationDescription)desc, "Invalid description type." ); 320 } 321 body 322 { 323 auto t = cast(T)comp; 324 auto newDesc = cast(SerializationDescription)desc; 325 326 foreach( fieldName; __traits( allMembers, T ) ) 327 { 328 enum idx = fieldList.map!(f => f.name).countUntil( fieldName ); 329 static if( idx >= 0 ) 330 { 331 enum field = fieldList[ idx ]; 332 // Check if the field was actually changed, and that it hasn't changed on component 333 if( mixin( field.name ) == mixin( "newDesc." ~ field.name ) ) 334 { 335 // Copy the value into this description 336 mixin( "this."~field.name~" = newDesc."~field.name~";" ); 337 // Deserialize it for the component 338 mixin( "auto ser = "~field.serializer~".deserialize(newDesc."~field.name~");" ); 339 // Copy the new value to the component 340 mixin( "t."~field.name~" = ser;" ); 341 } 342 } 343 } 344 } 345 346 /// Create a component from a description. 347 override T createInstance() const 348 { 349 T comp = new T; 350 comp.lastDesc = cast()this; 351 352 foreach( fieldName; __traits( allMembers, T ) ) 353 { 354 enum idx = fieldList.map!(f => f.name).countUntil( fieldName ); 355 static if( idx >= 0 ) 356 { 357 enum field = fieldList[ idx ]; 358 // Check if the field was actually set 359 if( mixin( field.name ) != mixin( "new SerializationDescription()." ~ field.name ) ) 360 { 361 // Deserialize it for the component 362 mixin( "auto rep = cast("~field.serializer~".Rep)this."~field.name~";" ); 363 mixin( "auto ser = "~field.serializer~".deserialize(rep);" ); 364 mixin( "comp."~field.name~" = ser;" ); 365 } 366 } 367 } 368 return comp; 369 } 370 371 /// Get the type of the component the description is for. 372 override ClassInfo componentType() @property const 373 { 374 return typeid(T); 375 } 376 } // SerializationDescription 377 378 /// Get a list of fields on the type 379 Description.Field[] getFields( size_t idx = 0 )( Description.Field[] fields = [] ) 380 { 381 static if( idx == __traits( allMembers, T ).length ) 382 { 383 return fields; 384 } 385 else 386 { 387 enum memberName = helper!( __traits( allMembers, T )[ idx ] ); 388 389 // Make sure member is accessable and that we care about it 390 static if( !memberName.among( "this", "~this", __traits( allMembers, Component ) ) && 391 is( typeof( helper!( __traits( getMember, T, memberName ) ) ) ) ) 392 { 393 alias member = helper!( __traits( getMember, T, memberName ) ); 394 alias memberType = typeof(member); 395 396 // Process variables 397 static if( isSerializableField!member ) 398 { 399 // Get string form of attributes 400 string attributesStr() 401 { 402 import std.conv; 403 string[] attrs; 404 foreach( attr; __traits( getAttributes, member ) ) 405 { 406 attrs ~= attr.to!string; 407 } 408 return attrs.join( ", " ).to!string; 409 } 410 411 template SerializedType( T ) 412 { 413 static if( is( T U : U[] ) ) 414 alias SerializedType = SerializedType!U; 415 else 416 alias SerializedType = T; 417 } 418 419 // Get required module import name 420 static if( __traits( compiles, moduleName!( SerializedType!memberType ) ) ) 421 enum modName = moduleName!( SerializedType!memberType ); 422 else 423 enum modName = null; 424 425 // Get the serializer for the type 426 alias serializer = serializerFor!memberType; 427 alias descMemberType = serializer.Rep; 428 // Generate field 429 return getFields!( idx + 1 )( fields ~ 430 Description.Field( 431 memberName, 432 fullyQualifiedName!(Unqual!descMemberType), 433 attributesStr, 434 modName, 435 serializer.stringof 436 ) 437 ); 438 } 439 else 440 { 441 return getFields!( idx + 1 )( fields ); 442 } 443 } 444 else 445 { 446 return getFields!( idx + 1 )( fields ); 447 } 448 } 449 } 450 }