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 }