1 /**
2  * Port of Ash Entity system into D
3  *
4  */
5 
6 module ashd.core.entity;
7 
8 import std.conv   : to;
9 import std.signals; // for Signal template.
10 
11 import ashd.core.component : Component;
12 
13 /**
14  * An entity is composed of components. As such, it is essentially a collection object for components.
15  * Sometimes, the entities in a game will mirror the actual characters and objects in the game, but this
16  * is not necessary.
17  *
18  * <p>Components are simple value objects that contain data relevant to the entity. Entities
19  * with similar functionality will have instances of the same components. So we might have
20  * a position component</p>
21  *
22  * <p><code>public class PositionComponent
23  * {
24  *   public var x : Number;
25  *   public var y : Number;
26  * }</code></p>
27  *
28  * <p>All entities that have a position in the game world, will have an instance of the
29  * position component. Systems operate on entities based on the components they have.</p>
30  */
31 public class Entity
32 {
33     /**
34      * The constructor
35      *
36      * Params:
37      *  name = The name for the entity. If left blank, a default name is assigned
38      *         with the form _entityN where N is an integer.
39      */
40     public this( string name = string.init ) nothrow @safe
41     {
42         if ( name != string.init )
43             mName = name;
44         else
45             mName = "_entity"~to!string(++mNameCount);
46     }
47 
48 
49     @property
50     {
51         Entity next()     nothrow @safe { return mNext; }
52         Entity previous() nothrow @safe { return mPrevious; }
53         void   next( Entity entity )     nothrow @safe { mNext = entity; }
54         void   previous( Entity entity ) nothrow @safe { mPrevious = entity; }
55 
56         /**
57          * All entities have a name. If no name is set, a default name is used. Names are used to
58          * fetch specific entities from the engine, and can also help to identify an entity when debugging.
59          */
60         public string name() nothrow @safe { return mName; }
61         public void name( string value )
62         {
63             if ( value == string.init )
64                 throw new Exception("Setting Entity name to empty string");
65 
66             if ( mName != value )
67             {
68                 string previous = mName;
69                 mName = value;
70                 nameChanged.emit( this, previous );
71             }
72         }
73     }
74 
75     /// This signal is dispatched when a component is added to the entity.
76     mixin Signal!( Entity, ClassInfo ) componentAdded;
77 
78     /// This signal is dispatched when a component is removed from the entity.
79     mixin Signal!( Entity, ClassInfo ) componentRemoved;
80 
81     /// Dispatched when the name of the entity changes.
82     /// Used internally by the engine to track entities based on their names.
83     mixin Signal!( Entity, string ) nameChanged;
84 
85 
86     /**
87      * Add a component to the entity.
88      *
89      *   If a component of the same type is already attached, it will be removed
90      * and replaced by the supplied component.
91      *
92      * Params:
93      *  component_a = The component object to add.
94      *  T = the type of component it is to be stored as
95      *
96      * Returns:
97      *  A reference to the entity. This enables the chaining of calls to add, to make
98      * creating and configuring entities cleaner. e.g.
99      *
100      * Entity entity = new Entity()
101      * entity.add( new Position( 100, 200 ) )
102      * entity.add( new Display( new PlayerClip() ) );
103      */
104     public Entity add(T)(T component)
105     {
106         if (T.classinfo in mComponents)
107             this.removeCpt(T.classinfo);
108 
109         mComponents[T.classinfo] = component;
110         componentAdded.emit(this, T.classinfo);
111 
112         return this;
113     }
114 
115     public Entity add(Object component, ClassInfo class_info)
116     {
117 
118         if (class_info in mComponents)
119             this.removeCpt(class_info);
120 
121         mComponents[class_info] = component;
122         componentAdded.emit(this, class_info);
123 
124         return this;
125     }
126 
127     // Quicker internal removal method
128     private void removeCpt(ClassInfo class_info)
129     {
130         mComponents.remove(class_info);
131         componentRemoved.emit(this, class_info);
132     }
133 
134 
135     /**
136      * Remove a component from the entity.
137      *
138      * Params:
139      *  T = The class of the component to be removed.
140      *
141      * Returns:
142      *  the component, or null if the component doesn't exist in the entity
143      */
144     public Object remove(T)()
145     {
146         T c;
147 
148         if ( T.classinfo in mComponents )
149         {
150             c = to!T(mComponents[T.classinfo]);
151             this.removeCpt( T.classinfo );
152             return c;
153         }
154         return null;
155     }
156 
157     public Object remove( ClassInfo class_a )
158     {
159         Object c;
160 
161         if ( class_a in mComponents )
162         {
163             c = cast(Object)(mComponents[class_a]);
164             this.removeCpt( class_a );
165             return c;
166         }
167         return null;
168     }
169 
170 
171     /**
172      * Get a component from the entity.
173      *
174      * @param componentClass The class of the component requested.
175      * @return The component, or null if none was found.
176      */
177     public T get(T)()
178     {
179         if ( T.classinfo in mComponents )
180         {
181             return to!T(mComponents[ T.classinfo ]);
182         }
183         else
184         {
185             return null;
186         }
187     }
188      /**
189      * Get a component from the entity.
190      *
191      * @param componentClass The class of the component requested.
192      * @return The component, or null if none was found.
193      */
194     public Object get( ClassInfo class_a )
195     {
196         if ( class_a in mComponents )
197             return mComponents[ class_a ];
198         else
199             return null;
200     }
201 
202     /**
203      * Get all components from the entity.
204      *
205      * Returns:
206      *  An array containing all the components that are on the entity.
207      */
208     public Object[] getAll()
209     {
210         Object[] componentArray;
211         ulong i;
212 
213         componentArray.length = mComponents.length;
214         foreach( component; mComponents )
215         {
216             componentArray[i++] = component;
217         }
218         return componentArray;
219     }
220 
221     /**
222      * Does the entity have a component of a particular type.
223      *
224      * @param componentClass The class of the component sought.
225      * @return true if the entity has a component of the type, false if not.
226      */
227     public bool has(T)()
228     {
229         return ( T.classinfo in mComponents ) !is null;
230     }
231 
232     public bool has( ClassInfo class_a )
233     {
234         return ( class_a in mComponents ) !is null;
235     }
236 
237 private:
238     string            mName;         // Optional, give the entity a name.
239                                      // This can help with debugging and with serialising the entity.
240     static int        mNameCount;    // Used if name not specified
241     Object[ClassInfo] mComponents;   // List of components attached to entity
242 
243     // Used by the EntityList class to form linked list
244     Entity            mPrevious;
245     Entity            mNext;
246 
247 
248 } // class Entity
249 
250