View Javadoc

1   /*
2    * Entreri, an entity-component framework in Java
3    *
4    * Copyright (c) 2013, Michael Ludwig
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without modification,
8    * are permitted provided that the following conditions are met:
9    *
10   *     Redistributions of source code must retain the above copyright notice,
11   *         this list of conditions and the following disclaimer.
12   *     Redistributions in binary form must reproduce the above copyright notice,
13   *         this list of conditions and the following disclaimer in the
14   *         documentation and/or other materials provided with the distribution.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package com.lhkbob.entreri.impl;
28  
29  import com.lhkbob.entreri.Component;
30  import com.lhkbob.entreri.ComponentIterator;
31  import com.lhkbob.entreri.Entity;
32  import com.lhkbob.entreri.EntitySystem;
33  import com.lhkbob.entreri.property.Property;
34  import com.lhkbob.entreri.property.PropertyFactory;
35  import com.lhkbob.entreri.task.Scheduler;
36  
37  import java.util.*;
38  
39  /**
40   * Main implementation of EntitySystem.
41   *
42   * @author Michael Ludwig
43   */
44  public final class EntitySystemImpl implements EntitySystem {
45      // converts valid component data types into indices into componentRepositories
46      private final Map<Class<? extends Component>, Integer> typeIndexMap;
47      private int typeIdSeq;
48  
49      private ComponentRepository<?>[] componentRepositories;
50  
51      private EntityImpl[] entities;
52  
53      private int entityInsert;
54      private int entityIdSeq;
55  
56      private final Scheduler manager;
57  
58      /**
59       * Create a new EntitySystem that has no entities added.
60       */
61      public EntitySystemImpl() {
62          typeIndexMap = new HashMap<>();
63          typeIdSeq = 0;
64  
65          manager = new Scheduler(this);
66          entities = new EntityImpl[1];
67          componentRepositories = new ComponentRepository[0];
68  
69          entityIdSeq = 1; // start at 1, id 0 is reserved for index = 0
70          entityInsert = 1;
71      }
72  
73      @Override
74      @SuppressWarnings({ "rawtypes", "unchecked" })
75      public <T extends Component> Collection<Class<? extends T>> getComponentTypes(
76              Class<T> type) {
77          if (type == null) {
78              throw new NullPointerException("Type cannot be null");
79          }
80  
81          List<Class<? extends T>> ids = new ArrayList<>();
82          for (int i = 0; i < componentRepositories.length; i++) {
83              if (componentRepositories[i] != null) {
84                  // check the type
85                  if (type.isAssignableFrom(componentRepositories[i].getType())) {
86                      // this type is a subclass of the requested type
87                      ids.add((Class) componentRepositories[i].getType());
88                  }
89              }
90          }
91          return ids;
92      }
93  
94      @Override
95      public Collection<Class<? extends Component>> getComponentTypes() {
96          List<Class<? extends Component>> ids = new ArrayList<>();
97          for (int i = 0; i < componentRepositories.length; i++) {
98              if (componentRepositories[i] != null) {
99                  ids.add(componentRepositories[i].getType());
100             }
101         }
102         return ids;
103     }
104 
105     @Override
106     public Scheduler getScheduler() {
107         return manager;
108     }
109 
110     @Override
111     public Iterator<Entity> iterator() {
112         return new EntityIterator();
113     }
114 
115     @Override
116     public <T extends Component> Iterator<T> iterator(Class<T> type) {
117         return new ComponentIteratorWrapper<>(type);
118     }
119 
120     @Override
121     public void compact() {
122         // Pack the data
123         int startRemove = -1;
124         for (int i = 1; i < entityInsert; i++) {
125             if (entities[i] == null) {
126                 // found an entity to remove
127                 if (startRemove < 0) {
128                     startRemove = i;
129                 }
130             } else {
131                 // found an entity to preserve
132                 if (startRemove >= 0) {
133                     // we have a gap from [startRemove, i - 1] that can be compacted
134                     System.arraycopy(entities, i, entities, startRemove,
135                                      entityInsert - i);
136 
137                     // update entityInsert
138                     entityInsert = entityInsert - i + startRemove;
139 
140                     // now reset loop
141                     i = startRemove;
142                     startRemove = -1;
143                 }
144             }
145         }
146 
147         if (startRemove >= 0) {
148             // the last gap of entities to remove is at the end of the array,
149             // so all we have to do is update the size
150             entityInsert = startRemove;
151         }
152 
153         // Build a map from oldIndex to newIndex and repair entity's index
154         int[] oldToNew = new int[entities.length];
155         for (int i = 1; i < entityInsert; i++) {
156             oldToNew[entities[i].index] = i;
157             entities[i].index = i;
158         }
159 
160         if (entityInsert < .6f * entities.length) {
161             // reduce the size of the entities/ids arrays
162             int newSize = (int) (1.2f * entityInsert) + 1;
163             entities = Arrays.copyOf(entities, newSize);
164         }
165 
166         // Now index and update all ComponentIndices
167         for (int i = 0; i < componentRepositories.length; i++) {
168             if (componentRepositories[i] != null) {
169                 componentRepositories[i].compact(oldToNew, entityInsert);
170             }
171         }
172     }
173 
174     @Override
175     public Entity addEntity() {
176         return addEntity(null);
177     }
178 
179     @Override
180     public Entity addEntity(Entity template) {
181         if (template != null) {
182             // validate the template before allocating a new entity
183             if (!template.isAlive()) {
184                 throw new IllegalStateException("Entity template is not live");
185             }
186         }
187 
188         int entityIndex = entityInsert++;
189         if (entityIndex >= entities.length) {
190             entities = Arrays.copyOf(entities, (int) (entityIndex * 1.5f) + 1);
191         }
192 
193         for (int i = 0; i < componentRepositories.length; i++) {
194             if (componentRepositories[i] != null) {
195                 componentRepositories[i].expandEntityIndex(entityIndex + 1);
196             }
197         }
198 
199         EntityImpl newEntity = new EntityImpl(this, entityIndex, entityIdSeq++);
200         entities[entityIndex] = newEntity;
201 
202         if (template != null) {
203             for (Component c : template) {
204                 addFromTemplate(entityIndex, c.getType(), c);
205             }
206         }
207 
208         return newEntity;
209     }
210 
211     @Override
212     public void removeEntity(Entity e) {
213         if (e == null) {
214             throw new NullPointerException("Cannot remove a null entity");
215         }
216         if (e.getEntitySystem() != this) {
217             throw new IllegalArgumentException("Entity is not from this EntitySystem");
218         }
219 
220         EntityImpl ei = (EntityImpl) e;
221         if (ei.index == 0) {
222             throw new IllegalArgumentException("Entity has already been removed");
223         }
224 
225         // Handle ownership removals
226         ei.setOwner(null);
227         ei.delegate.disownAndRemoveChildren();
228 
229         // Remove all components from the entity (that weren't removed
230         // by ownership rules)
231         for (int i = 0; i < componentRepositories.length; i++) {
232             if (componentRepositories[i] != null) {
233                 componentRepositories[i].removeComponent(ei.index);
234             }
235         }
236 
237         // clear out the entity
238         entities[ei.index] = null;
239         ei.index = 0;
240     }
241 
242     @Override
243     public <T extends Component, P extends Property> P decorate(Class<T> type,
244                                                                 PropertyFactory<P> factory) {
245         ComponentRepository<?> index = getRepository(type);
246         return index.decorate(factory);
247     }
248 
249     @Override
250     public ComponentIterator fastIterator() {
251         return new ComponentIteratorImpl(this);
252     }
253 
254     /**
255      * Return the ComponentRepository associated with the given type. Creates a new
256      * component repository if the type hasn't been used or accessed before.
257      *
258      * @param <T>  The Component type
259      * @param type The component type
260      *
261      * @return The ComponentRepository for the type
262      */
263     @SuppressWarnings("unchecked")
264     <T extends Component> ComponentRepository<T> getRepository(Class<T> type) {
265         int index = getTypeIndex(type);
266         if (index >= componentRepositories.length) {
267             // make sure it's the correct size
268             componentRepositories = Arrays.copyOf(componentRepositories, index + 1);
269         }
270 
271         ComponentRepository<T> i = (ComponentRepository<T>) componentRepositories[index];
272         if (i == null) {
273             // if the index does not exist, then we need to use the default component data factory
274             i = new ComponentRepository<>(this, type);
275             i.expandEntityIndex(entities.length);
276             componentRepositories[index] = i;
277         }
278 
279         return i;
280     }
281 
282     private int getTypeIndex(Class<? extends Component> type) {
283         Integer id = typeIndexMap.get(type);
284         if (id == null) {
285             id = typeIdSeq++;
286             typeIndexMap.put(type, id);
287         }
288         return id;
289     }
290 
291     /**
292      * @return Return an iterator over the registered component indices
293      */
294     Iterator<ComponentRepository<?>> indexIterator() {
295         return new ComponentRepositoryIterator();
296     }
297 
298     /**
299      * Return the canonical Entity instance associated with the given index.
300      *
301      * @param entityIndex The index that the entity is stored at within the entity array
302      *                    and component indicees
303      *
304      * @return The canonical Entity instance for the index
305      */
306     Entity getEntityByIndex(int entityIndex) {
307         return entities[entityIndex];
308     }
309 
310     @SuppressWarnings({ "rawtypes", "unchecked" })
311     private <T extends Component> void addFromTemplate(int entityIndex, Class type, T c) {
312         ComponentRepository index = getRepository(type);
313         index.addComponent(entityIndex, c);
314     }
315 
316     private class ComponentRepositoryIterator
317             implements Iterator<ComponentRepository<?>> {
318         private int index;
319         private boolean advanced;
320 
321         public ComponentRepositoryIterator() {
322             index = -1;
323             advanced = false;
324         }
325 
326         @Override
327         public boolean hasNext() {
328             if (!advanced) {
329                 advance();
330             }
331             return index < componentRepositories.length;
332         }
333 
334         @Override
335         public ComponentRepository<?> next() {
336             if (!hasNext()) {
337                 throw new NoSuchElementException();
338             }
339             advanced = false;
340             return componentRepositories[index];
341         }
342 
343         @Override
344         public void remove() {
345             throw new UnsupportedOperationException();
346         }
347 
348         private void advance() {
349             do {
350                 index++;
351             } while (index < componentRepositories.length &&
352                      componentRepositories[index] == null);
353             advanced = true;
354         }
355     }
356 
357     private class ComponentIteratorWrapper<T extends Component> implements Iterator<T> {
358         private final T data;
359         private final ComponentIterator it;
360 
361         private boolean nextCalled;
362         private boolean hasNext;
363 
364         public ComponentIteratorWrapper(Class<T> type) {
365             it = fastIterator();
366             data = it.addRequired(type);
367 
368             nextCalled = false;
369             hasNext = false;
370         }
371 
372         @Override
373         public boolean hasNext() {
374             if (!nextCalled) {
375                 hasNext = it.next();
376                 nextCalled = true;
377             }
378             return hasNext;
379         }
380 
381         @Override
382         public T next() {
383             if (!hasNext()) {
384                 throw new NoSuchElementException();
385             }
386             nextCalled = false;
387             return data;
388         }
389 
390         @Override
391         public void remove() {
392             throw new UnsupportedOperationException();
393         }
394     }
395 
396     private class EntityIterator implements Iterator<Entity> {
397         private int index;
398         private boolean advanced;
399 
400         public EntityIterator() {
401             index = 0;
402             advanced = false;
403         }
404 
405         @Override
406         public boolean hasNext() {
407             if (!advanced) {
408                 advance();
409             }
410             return index < entityInsert;
411         }
412 
413         @Override
414         public Entity next() {
415             if (!hasNext()) {
416                 throw new NoSuchElementException();
417             }
418             advanced = false;
419             return entities[index];
420         }
421 
422         @Override
423         public void remove() {
424             if (advanced || index == 0) {
425                 throw new IllegalStateException("Must call next() before remove()");
426             }
427             if (entities[index] == null) {
428                 throw new IllegalStateException("Entity already removed");
429             }
430             removeEntity(entities[index]);
431         }
432 
433         private void advance() {
434             do {
435                 index++; // always advance at least 1
436             } while (index < entities.length && entities[index] == null);
437             advanced = true;
438         }
439     }
440 }