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 }