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.Entity;
31  import com.lhkbob.entreri.Requires;
32  import com.lhkbob.entreri.property.IntProperty;
33  import com.lhkbob.entreri.property.ObjectProperty;
34  import com.lhkbob.entreri.property.Property;
35  import com.lhkbob.entreri.property.PropertyFactory;
36  
37  import java.lang.ref.WeakReference;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Iterator;
41  import java.util.List;
42  
43  /**
44   * ComponentRepository manages storing all the componentDatas of a specific type for an
45   * EntitySystem. It also controls the IndexedDataStore's for the type's set of properties.
46   * It is package-private because its details are low-level and complex.
47   *
48   * @param <T> The type of component stored by the index
49   *
50   * @author Michael Ludwig
51   */
52  @SuppressWarnings({ "unchecked", "rawtypes" })
53  public final class ComponentRepository<T extends Component> {
54      private final EntitySystemImpl system;
55      private final Class<T> type;
56  
57      private final ComponentFactoryProvider.Factory<T> factory;
58      private final Class<? extends Component>[] requiredTypes;
59  
60      // These three arrays have a special value of 0 or null stored in the 0th
61      // index, which allows us to lookup componentDatas or entities when they
62      // normally aren't attached.
63      private int[] entityIndexToComponentRepository;
64      private int[] componentIndexToEntityIndex;
65      private T[] components;
66      private int componentInsert;
67  
68      private final List<DeclaredPropertyStore<?>> declaredProperties;
69      private final List<DecoratedPropertyStore<?>> decoratedProperties;
70  
71      // this is contained in decoratedProperties
72      private final IntProperty componentIdProperty;
73      private final IntProperty componentVersionProperty;
74  
75      private final ObjectProperty ownerDelegatesProperty;
76  
77      private int idSeq;
78      private int versionSeq;
79  
80      /**
81       * Create a ComponentRepository for the given system, that will store Components of
82       * the given type.
83       *
84       * @param system The owning system
85       * @param type   The type of component
86       *
87       * @throws NullPointerException if system or type are null
88       */
89      public ComponentRepository(EntitySystemImpl system, Class<T> type) {
90          if (system == null || type == null) {
91              throw new NullPointerException("Arguments cannot be null");
92          }
93  
94          this.system = system;
95          this.factory = ComponentFactoryProvider.getInstance().getFactory(type);
96          this.type = type;
97  
98          if (type.getAnnotation(Requires.class) != null) {
99              requiredTypes = type.getAnnotation(Requires.class).value();
100         } else {
101             requiredTypes = new Class[0];
102         }
103 
104         declaredProperties = new ArrayList<>();
105         decoratedProperties = new ArrayList<>(); // empty for now
106         for (PropertyDeclaration p : factory.getSpecification().getProperties()) {
107             DeclaredPropertyStore store = new DeclaredPropertyStore(
108                     p.getPropertyFactory(), p.getName());
109             declaredProperties.add(store);
110         }
111 
112         entityIndexToComponentRepository = new int[1]; // holds default 0 value in 0th index
113         componentIndexToEntityIndex = new int[1]; // holds default 0 value in 0th index
114         components = (T[]) new Component[1]; // holds default null value in 0th index
115 
116         componentInsert = 1;
117 
118         // Make sure properties' stores hold enough space
119         resizePropertyStores(declaredProperties, 1);
120 
121         // decorate the component data with a boolean property to track enabled status
122         // we set a unique id for every component
123         componentIdProperty = decorate(new IntProperty.Factory(0));
124         componentVersionProperty = decorate(new IntProperty.Factory(0));
125         ownerDelegatesProperty = decorate(ObjectProperty.<OwnerSupport>factory(null));
126 
127         idSeq = 1; // start at 1, just like entity id sequences versionSeq = 0;
128 
129         // initialize version for the 0th index
130         componentVersionProperty.set(0, -1);
131     }
132 
133     /**
134      * @return The type of component data stored by this component index
135      */
136     public Class<T> getType() {
137         return type;
138     }
139 
140     /**
141      * @return The upper bound (exclusive) for component index values
142      */
143     public int getMaxComponentIndex() {
144         return componentInsert;
145     }
146 
147     /**
148      * @return The owning EntitySystem
149      */
150     public EntitySystemImpl getEntitySystem() {
151         return system;
152     }
153 
154     /**
155      * Given the index of a Component (e.g. {@link Component#getIndex()}, return the index
156      * of an entity within the owning system. The returned entity index can be safely
157      * passed to {@link EntitySystemImpl#getEntityByIndex(int)}.
158      *
159      * @param componentIndex The component index whose owning entity is fetched
160      *
161      * @return The index of the entity that has the given component index, or 0 if the
162      *         component is not attached
163      */
164     public int getEntityIndex(int componentIndex) {
165         return componentIndexToEntityIndex[componentIndex];
166     }
167 
168     /**
169      * Given the index of an entity (e.g. {@link EntityImpl#index}), return the index of
170      * the attached component of this ComponentRepository's type. The returned component
171      * index can be used in {@link #getComponent(int)} and related methods.
172      *
173      * @param entityIndex The entity index to look up
174      *
175      * @return The index of the attached component, or 0 if the entity does not have a
176      *         component of this type attached
177      */
178     public int getComponentIndex(int entityIndex) {
179         return entityIndexToComponentRepository[entityIndex];
180     }
181 
182     /**
183      * Ensure that this ComponentRepository has enough internal space to hold its
184      * entity-to-component mapping for the given number of entities.
185      *
186      * @param numEntities The new number of entities
187      */
188     public void expandEntityIndex(int numEntities) {
189         if (entityIndexToComponentRepository.length < numEntities) {
190             entityIndexToComponentRepository = Arrays
191                     .copyOf(entityIndexToComponentRepository,
192                             (int) (numEntities * 1.5) + 1);
193         }
194     }
195 
196     /**
197      * @param componentIndex The component index
198      *
199      * @return The component id of the component at the given index
200      */
201     public int getId(int componentIndex) {
202         return componentIdProperty.get(componentIndex);
203     }
204 
205     /**
206      * @param componentIndex The component index
207      *
208      * @return The component version of the component at the given index
209      */
210     public int getVersion(int componentIndex) {
211         return componentVersionProperty.get(componentIndex);
212     }
213 
214     /**
215      * Increment the component's version at the given index. This does nothing if the
216      * index is 0, preserving the guarantee that an invalid component has a negative
217      * version.
218      *
219      * @param componentIndex The component to update
220      */
221     public void incrementVersion(int componentIndex) {
222         if (componentIndex != 0) {
223             // clamp it to be above 0, instead of going negative
224             int newVersion = (0xefffffff & (versionSeq++));
225             componentVersionProperty.set(componentIndex, newVersion);
226         }
227     }
228 
229     /**
230      * @param componentIndex The component index
231      *
232      * @return The OwnerSupport delegate for the component by the given index
233      */
234     public OwnerSupport getOwnerDelegate(int componentIndex) {
235         return (OwnerSupport) ownerDelegatesProperty.get(componentIndex);
236     }
237 
238     /**
239      * @param propertyIndex The index of the property, which is the corresponding index
240      *                      from the property specification of the component type
241      *
242      * @return The declared property used for the given index by this repository
243      */
244     public Property getProperty(int propertyIndex) {
245         return declaredProperties.get(propertyIndex).getProperty();
246     }
247 
248     /**
249      * @param propertyIndex The index of the property
250      *
251      * @return The logical name of the property
252      */
253     public String getDeclaredPropertyName(int propertyIndex) {
254         return declaredProperties.get(propertyIndex).key;
255     }
256 
257     /**
258      * @return The number of declared properties
259      */
260     public int getDeclaredPropertyCount() {
261         return declaredProperties.size();
262     }
263 
264     /*
265      * As expandEntityIndex() but expands all related component data and arrays
266      * to hold the number of components.
267      */
268     private void expandComponentRepository(int numComponents) {
269         if (numComponents < components.length) {
270             return;
271         }
272 
273         int size = (int) (numComponents * 1.5) + 1;
274 
275         // Expand the indexed data stores for the properties
276         resizePropertyStores(declaredProperties, size);
277         resizePropertyStores(decoratedProperties, size);
278 
279         // Expand the canonical component array
280         components = Arrays.copyOf(components, size);
281 
282         // Expand the component index
283         componentIndexToEntityIndex = Arrays.copyOf(componentIndexToEntityIndex, size);
284     }
285 
286     /*
287      * Convenience to create a new data store for each property with the given
288      * size, copy the old data over, and assign it back to the property.
289      */
290     private void resizePropertyStores(List<? extends PropertyStore<?>> properties,
291                                       int size) {
292         int ct = properties.size();
293         for (int i = 0; i < ct; i++) {
294             properties.get(i).resize(size);
295         }
296     }
297 
298     /**
299      * @param componentIndex The component index whose component is fetched
300      *
301      * @return The component reference at the given index, may be null
302      */
303     public T getComponent(int componentIndex) {
304         return components[componentIndex];
305     }
306 
307     /**
308      * Create a new component and attach to it the entity at the given entity index, the
309      * new component will have its values copied from the existing template.
310      *
311      * @param entityIndex  The entity index which the component is attached to
312      * @param fromTemplate A template to assign values to the new component
313      *
314      * @return A new component of type T
315      *
316      * @throws NullPointerException  if fromTemplate is null
317      * @throws IllegalStateException if the template is not live
318      */
319     public T addComponent(int entityIndex, T fromTemplate) {
320         if (!type.isInstance(fromTemplate)) {
321             throw new IllegalArgumentException(
322                     "Component not of expected type, expected: " + type + ", but was: " +
323                     fromTemplate.getClass());
324         }
325         if (!fromTemplate.isAlive()) {
326             throw new IllegalStateException("Template component is not live");
327         }
328 
329         T instance = addComponent(entityIndex);
330         // this is safe across systems because the property spec for a component type
331         // will be the same so the factories will be consistent and the order is the
332         // same since they're reported in name order
333         for (int i = 0; i < declaredProperties.size(); i++) {
334             DeclaredPropertyStore dstStore = declaredProperties.get(i);
335             DeclaredPropertyStore templateStore = ((AbstractComponent<T>) fromTemplate)
336                     .owner.declaredProperties.get(i);
337 
338             templateStore.creator
339                          .clone(templateStore.getProperty(), fromTemplate.getIndex(),
340                                 dstStore.property, instance.getIndex());
341         }
342 
343         return instance;
344     }
345 
346     /**
347      * Create a new component and attach to it the entity at the given entity index. The
348      * component will have the default state as specified by its properties.
349      *
350      * @param entityIndex The entity index which the component is attached to
351      *
352      * @return A new component of type T
353      *
354      * @throws IllegalArgumentException if initParams is incorrect
355      */
356     public T addComponent(int entityIndex) {
357         if (entityIndexToComponentRepository[entityIndex] != 0) {
358             removeComponent(entityIndex);
359         }
360 
361         int componentIndex = componentInsert++;
362         if (componentIndex >= components.length) {
363             expandComponentRepository(componentIndex + 1);
364         }
365 
366         AbstractComponent<T> instance = factory.newInstance(this);
367         components[componentIndex] = (T) instance;
368         componentIndexToEntityIndex[componentIndex] = entityIndex;
369         entityIndexToComponentRepository[entityIndex] = componentIndex;
370 
371         // Set default value for declared and decorated properties,
372         // this is needed because we might be overwriting a previously removed
373         // component, or the factory might be doing something tricky
374         for (int i = 0; i < declaredProperties.size(); i++) {
375             declaredProperties.get(i).setDefaultValue(componentIndex);
376         }
377         for (int i = 0; i < decoratedProperties.size(); i++) {
378             decoratedProperties.get(i).setDefaultValue(componentIndex);
379         }
380 
381         // although there could be a custom PropertyFactory for setting the id,
382         // it's easier to assign a new id here
383         componentIdProperty.set(componentIndex, idSeq++);
384         // same goes for assigning a new owner delegate
385         ownerDelegatesProperty.set(componentIndex, new OwnerSupport(instance));
386 
387         // start with a unique version as well
388         incrementVersion(componentIndex);
389 
390         // connect component back to the index too
391         instance.setIndex(componentIndex);
392 
393         // ensure required components are added as well
394         Entity entity = system.getEntityByIndex(entityIndex);
395         for (int i = 0; i < requiredTypes.length; i++) {
396             if (entity.get((Class) requiredTypes[i]) == null) {
397                 Component added = entity.add((Class) requiredTypes[i]);
398                 added.setOwner(instance);
399             }
400         }
401 
402         return (T) instance;
403     }
404 
405     /**
406      * Create a new ComponentData of type T that can be used to view components in this
407      * index.
408      *
409      * @return A new data instance
410      */
411     public AbstractComponent<T> createDataInstance() {
412         AbstractComponent<T> t = factory.newInstance(this);
413         t.setIndex(0);
414         return t;
415     }
416 
417     /**
418      * Detach or remove any component of this index's type from the entity with the given
419      * index. True is returned if a component was removed, or false otherwise.
420      *
421      * @param entityIndex The entity's index whose component is removed
422      *
423      * @return True if a component was removed
424      */
425     public boolean removeComponent(int entityIndex) {
426         int componentIndex = entityIndexToComponentRepository[entityIndex];
427 
428         // This code works even if componentIndex is 0
429         T oldComponent = components[componentIndex];
430         AbstractComponent<T> casted = (AbstractComponent<T>) oldComponent;
431         if (oldComponent != null) {
432             oldComponent.setOwner(null);
433             getOwnerDelegate(componentIndex).disownAndRemoveChildren();
434             casted.setIndex(0);
435         }
436 
437         components[componentIndex] = null;
438         entityIndexToComponentRepository[entityIndex] = 0; // entity does not have component
439         componentIndexToEntityIndex[componentIndex] = 0; // component does not have entity
440         componentIdProperty.set(componentIndex, 0); // clear id
441         ownerDelegatesProperty.set(componentIndex, null);
442 
443         return oldComponent != null;
444     }
445 
446     private void sort() {
447         // perform an insertion sort, since most components are likely to be
448         // ordered correctly the performance will be almost linear
449 
450         for (int i = 1; i < componentInsert; i++) {
451             int vi = componentIndexToEntityIndex[i];
452             for (int j = i - 1; j >= 1; j--) {
453                 int vj = componentIndexToEntityIndex[j];
454 
455                 // move an index left if it is valid and it's less than
456                 // the prior index or if the prior index is invalid
457                 if (vi > 0 && (vi < vj || vj == 0)) {
458                     // must swap in place
459                     componentIndexToEntityIndex[j] = vi;
460                     componentIndexToEntityIndex[j + 1] = vj;
461 
462                     T t = components[j];
463                     components[j] = components[j + 1];
464                     components[j + 1] = t;
465 
466                     // keep property data inline with components
467                     swap(declaredProperties, j + 1, j);
468                     swap(decoratedProperties, j + 1, j);
469                 } else {
470                     // reached proper point in sorted sublist
471                     break;
472                 }
473             }
474         }
475     }
476 
477     private void swap(List<? extends PropertyStore<?>> store, int a, int b) {
478         for (int i = 0; i < store.size(); i++) {
479             store.get(i).swap(a, b);
480         }
481     }
482 
483     /**
484      * <p/>
485      * Compact the data of this ComponentRepository to account for removals and additions
486      * to the index. This will ensure that all active componentDatas are packed into the
487      * underlying arrays, and that they will be accessed in the same order as iterating
488      * over the entities directly.
489      * <p/>
490      * The map from old to new entity index must be used to properly update the component
491      * index's data so that the system is kept in sync.
492      *
493      * @param entityOldToNewMap A map from old entity index to new index
494      * @param numEntities       The number of entities that are in the system
495      */
496     public void compact(int[] entityOldToNewMap, int numEntities) {
497         // Remove all WeakPropertyStores that no longer have a property
498         Iterator<DecoratedPropertyStore<?>> it = decoratedProperties.iterator();
499         while (it.hasNext()) {
500             if (it.next().getProperty() == null) {
501                 it.remove();
502             }
503         }
504 
505         // Sort the canonical components array to order them by their entity, which
506         // also keeps the property data valid
507         sort();
508 
509         // Repair the componentToEntityIndex and the component.index values
510         componentInsert = 1;
511         for (int i = 1; i < components.length; i++) {
512             if (components[i] != null) {
513                 componentIndexToEntityIndex[i] = entityOldToNewMap[componentIndexToEntityIndex[i]];
514                 ((AbstractComponent<T>) components[i]).setIndex(i);
515                 componentInsert = i + 1;
516             } else {
517                 // we can terminate now since all future components should be null
518                 // since we've sorted it that way
519                 break;
520             }
521         }
522 
523         // Possibly compact the component data
524         if (componentInsert < .6 * components.length) {
525             int newSize = (int) (1.2 * componentInsert) + 1;
526             components = Arrays.copyOf(components, newSize);
527             componentIndexToEntityIndex = Arrays
528                     .copyOf(componentIndexToEntityIndex, newSize);
529             resizePropertyStores(declaredProperties, newSize);
530             resizePropertyStores(decoratedProperties, newSize);
531         }
532 
533         // Repair entityIndexToComponentRepository - and possible shrink the index
534         // based on the number of packed entities
535         if (numEntities < .6 * entityIndexToComponentRepository.length) {
536             entityIndexToComponentRepository = new int[(int) (1.2 * numEntities) + 1];
537         } else {
538             Arrays.fill(entityIndexToComponentRepository, 0);
539         }
540 
541         for (int i = 1; i < componentInsert; i++) {
542             entityIndexToComponentRepository[componentIndexToEntityIndex[i]] = i;
543         }
544     }
545 
546     /**
547      * Decorate the type information of this ComponentRepository to add a property created
548      * by the given factory. The returned property will have default data assigned for
549      * each current Component in the index, and will have the default value assigned for
550      * each new Component. Decorators can then access the returned property to manipulate
551      * the decorated component data.
552      *
553      * @param <P>     The type of property created
554      * @param factory The factory that will create a unique Property instance associated
555      *                with the decorated property and this index
556      *
557      * @return The property decorated onto the type of the index
558      */
559     public <P extends Property> P decorate(PropertyFactory<P> factory) {
560         int size = (declaredProperties.isEmpty() ? componentInsert
561                                                  : declaredProperties.get(0).property
562                             .getCapacity());
563         P prop = factory.create();
564         DecoratedPropertyStore<P> pstore = new DecoratedPropertyStore<>(factory, prop);
565 
566         // Set values from factory to all component slots
567         prop.setCapacity(size);
568         for (int i = 1; i < size; i++) {
569             pstore.setDefaultValue(i);
570         }
571 
572         decoratedProperties.add(pstore);
573         return prop;
574     }
575 
576     /*
577      * Type wrapping a key, property, and factory, as well as an auxiliary data
578      * store for compaction.
579      */
580     private static abstract class PropertyStore<P extends Property> {
581         final PropertyFactory<P> creator;
582 
583         PropertyStore(PropertyFactory<P> creator) {
584             this.creator = creator;
585         }
586 
587         void setDefaultValue(int index) {
588             P prop = getProperty();
589             if (prop != null) {
590                 creator.setDefaultValue(prop, index);
591             }
592         }
593 
594         void resize(int size) {
595             P property = getProperty();
596             if (property != null) {
597                 property.setCapacity(size);
598             }
599         }
600 
601         void swap(int a, int b) {
602             P property = getProperty();
603             if (property != null) {
604                 property.swap(a, b);
605             }
606         }
607 
608         abstract P getProperty();
609     }
610 
611     private static class DeclaredPropertyStore<P extends Property>
612             extends PropertyStore<P>
613 
614     {
615         final String key;
616         final P property;
617 
618         public DeclaredPropertyStore(PropertyFactory<P> creator, String key) {
619             super(creator);
620             this.key = key;
621             property = creator.create();
622         }
623 
624         @Override
625         P getProperty() {
626             return property;
627         }
628     }
629 
630     private static class DecoratedPropertyStore<P extends Property>
631             extends PropertyStore<P> {
632         final WeakReference<P> property;
633 
634         public DecoratedPropertyStore(PropertyFactory<P> creator, P property) {
635             super(creator);
636             this.property = new WeakReference<>(property);
637         }
638 
639         @Override
640         P getProperty() {
641             return property.get();
642         }
643     }
644 }