This is my first attempt to implement Entity Component System in my project and I'm not sure how some of its mechanics works. For example do I remove an entity? Since all systems are using entities list throughout whole game loop, every attempt of deleting element of that list is condemned to ConcurrentModificationException. Going by this advice I've tried to setting some kind of "toRemove" flag for entities and look for it every time system iterate through list
public class DrawingSystem extends System {
public DrawingSystem(List<Entity> entityList) {
super(entityList);
}
public void update(Batch batch) {
for (Entity entity : entityList) {
removeIfNeccesarry(entity);
//code
}
}
public void removeIfNeccesarry(Entity entity){
if(entity.toRemove){
entityList.remove(entity);
}
}
}
but that didn't help getting rid of the exception. I'm sure there is a elegant solution to this problem since this design pattern is broadly used but I'm just not aware of it.
Check out iterators:
"Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics."
https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Iterator.html
Iterator<Entity> it = entityList.iterator();
while (it.hasNext()) {
Entity entity = it.next();
if (...) {
it.remove();
}
}
You could also store the indices of the entities to remove somewhere outside the list and then remove the dead entities in an extra step after the update/render.
This has the advantage that you do not miss entities in later steps of your update.
Edit: Added code.
Related
I have a mongodb collection that needs to be cleaned before a certain process starts, i do this by using mongoTemplate.dropCollection() method becouse it is much faster than using the deleteAll() method on the repository.
The problem arises when i introduced indexes, my model is annotated as follows:
#Document
public class TestModel {
#Indexed
private String testField;
}
and repository
public interface TestModelRepository extends MongoRepository<TestModel, String> {
}
This makes sure that the index is created at application start time
i noticed that by using the repository's deleteAll() method instead of dropping the collection preserves the index, but i was wondering if there is a way with spring-data to make sure that indexes are in place when i make an insert.
also any method to re-create indexes based on the annotated model after drop would be appreciated.
something like
mongoTemplate.createIndexes( TestModel.class );
How can i achieve this?
There is no method like this
mongoTemplate.createIndexes( TestModel.class );
Before deleting, just get the indexInfo and after dropping collection, Recreate the indexes.
List<IndexInfo> indexInfo = mongoTemplate.indexOps(TestModel.class).getIndexInfo();
mongoTemplate.dropCollection(TestModel.class);
indexInfo.forEach(index -> {
DBObject indexOptions = new BasicDBObject();
if(! index.getName().equals("_id_"))
{
indexOptions.put(index.getName(), 1);
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(indexOptions);
indexDefinition.named(index.getName());
mongoTemplate.indexOps(TestModel.class).ensureIndex(indexDefinition);
}
});
This is my sample mapping in hibernate
class ApplnDoc {
AdmAppln admAppln;
// getters and setters
}
class AdmAppln {
Set<Student> student;
// getters and setters
}
class Student {
int id;
String registerNo;
AdmAppln admAppln;
// getters and setters
}
In ApplnDoc table we are storing images of all candidates. AdmAppln is for storing admission details, Student is for storing student details. Even if AdmAppln is having a Set of Student, only one record of Student will be present for a particular AdmAppln id (under one AdmAppln only one Student).
Now I want to write few data from to these tables, into an Excel file, whose records must be sorted in the order of registerNo (if it is present), otherwise using id of the Student. We are using XSSFWorkbook class under org.apache.poi.xssf.usermodel package for doing operations on Excel sheet. Here I found a way to sort the excel sheet, but I tried and found a way in code itself using Comparable interface.
This is what I did in ApplnDoc class
public int compareTo(ApplnDoc otherData) {
if(new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo() != null &&
!new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo().isEmpty() &&
new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo() != null &&
!new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo().isEmpty()) {
return new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo()
.compareTo
(new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo());
} else {
return new ArrayList<Student>(this.admAppln.getStudents()).get(0).getId() -
new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getId();
}
}
Since there is no get() method in Set interface the only way to get Student's registerNo from AdmAppln was to convert it to a list. Then I sorted the list and then it was iterated to generate the excel file.
Is the above mentioned comparison mechanism a proper one or is there a better way? Why I am asking this question because when the Hibernate session is closed and in my compareTo if I'm accessing the child table columns, then I will be getting Invocation exception.
There are some thing worth discussing here:
1-
Even if AdmAppln is having a Set of Student, only one record of
Student will be present for a particular AdmAppln
Why?
is this something you have no control over or is there any particular reason to keep a set where is not needed? (also im assuming a #OneToMany instead of a #OneToOne mapping)
2-
This lead to the child object beig lazy fetched (N.B this is an assumption since you didn't post relevant code about mappings or how you fetch the entity from db).
This means that you have to either switch to eager fetching in the entity (unrecommended) or specify it when fetching the entities
3-
Also please refactor that compareTo and use variables
public int compareTo(ApplnDoc otherData) {
Student thisStudent = new ArrayList<>(this.admAppln.getStudents()).get(0);
Student otherStudent = new ArrayList<>(otherData.admAppln.getStudents()).get(0);
if(thisStudent.getRegisterNo() != null &&
!thisStudent.getRegisterNo().isEmpty() &&
otherStudent.getRegisterNo() != null &&
!otherStudent.getRegisterNo().isEmpty()) {
return thisStudent.getRegisterNo().compareTo(otherStudent.getRegisterNo());
} else {
return thisStudent.getId() - otherStudent.getId();
}
}
While nothing wrong with that comparison mechanism (except the NullPointer if you have an empty Set of student) you should use the database ordering when querying.
If you still want to compare that way you just have to make sure you have everything you need fetched before closing the session.
You need to load the entire object tree before closing the session else you will get Exception. By the way you can always sort records with the query itself.
I'm trying to merge these three objects into a single complex object:
public class Person {
private String name;
private List<Event> events;
// getters and setters
}
public class Event {
private String name;
private List<Gift> gifts;
// getters and setters
}
public class Gift {
private String name;
private String recipient;// the name of the person
private String eventName;
// getters and setters
}
My goal is to save the Person object in MongoDB using Morphia and this how I want my document laid out. I've created a document builder, of sorts, that combines lists of each object. Each Person gets a list of all Events, but can only receive specific Gifts. While my document builder does create a document that Morphia can persist, only the Gifts of that last recipient (sort order) are inserted into the Events for all Persons. Though for the correct Events.
public void merge() {
for (Person person : listOfPersons) {
for (Event event : listOfEvents) {
// somePersonsGifts: a sublist of gifts based on Event and Person.
List<Gift> somePersonsGifts = new ArrayList<Gift>();
for (Gift gift : listOfGifts) {
if (person.getName().equals(gift.getRecipient()) && gift.getEventName().equals(event.getName())) {
somePersonsGifts.add(gift);
}
}
event.setGifts(somePersonsGifts);
}
person.setEvents(listOfEvents)
}
}
If I modify the code slightly to process one person at a time by removing the outer loop and having the method take an argument for specific index of the Persons list:
public void merge(int p) {
Person person = listOfPersons.get(p);
//...and so on
I get one complete Person object with the correct gifts. If try to feed the this modified version into a loop, the problem comes back. I've tried using regular for-loops and synchronized collections. I've tried using Google Guava's ImmutableArrayList and still no luck. I know the problem is that I'm changing the lists while accessing them but I can't find anyway around it. I wrote a DAO that uses the MongoDB driver directly and it works properly, but it's a lot more code and quite ugly. I really want this approach to work, the answer is in front of me but I just can't see it. Any help would be greatly appreciated.
Here is your problem:
List<Gift> somePersonsGifts = new ArrayList<Gift>();
....
event.setGifts(somePersonsGifts);
You add the gifts only for one person; if you want to aggregate all the gifts into the event, re-use the existing list.
I don't know anything about MongoDB or Morphia but I suspect the problem is your use of the setters event.setGifts(somePersonsGifts) and person.setEvents(events). Your code does not seem to merge the existing gift and event lists with the ones you are calculating further in the loop, which is how you would want it to behave (if I understand the question correctly).
You should retrieve the allready existing gift list (and event list too) instead of overwriting them with empty new ones.
I don't know if the method merge() is inside the list but I assume that since you are using the list events here
person.setEvents(events);
Maybe you meant
person.setEvents(listOfEvents)
Notice that you are adding all the events to each person. If all the persons went to all the events, it is unnecessary to have the events inside the person.
I need to update some fixed-priority elements in a PriorityQueue based on their ID. I think it's quite a common scenario, here's an example snippet (Android 2.2):
for (Entry e : mEntries) {
if (e.getId().equals(someId)) {
e.setData(newData);
}
}
I've then made Entry "immutable" (no setter methods) so that a new Entry instance is created and returned by setData(). I modified my method into this:
for (Entry e : mEntries) {
if (e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
mEntries.remove(e);
mEntries.add(newEntry);
}
}
The code seems to work fine, but someone pointed out that modifying a queue while iterating over it is a bad idea: it may throw a ConcurrentModificationException and I'd need to add the elements I want to remove to an ArrayList and remove it later. He didn't explain why, and it looks quite an overhead to me, but I couldn't find any specific explanation on internet.
(This post is similar, but there priorities can change, which is not my case)
Can anyone clarify what's wrong with my code, how should I change it and - most of all - why?
Thanks,
Rippel
PS: Some implementation details...
PriorityQueue<Entry> mEntries = new PriorityQueue<Entry>(1, Entry.EntryComparator());
with:
public static class EntryComparator implements Comparator<Entry> {
public int compare(Entry my, Entry their) {
if (my.mPriority < their.mPriority) {
return 1;
}
else if (my.mPriority > their.mPriority) {
return -1;
}
return 0;
}
}
This code is in the Java 6 implementation of PriorityQueue:
private class Itr implements Iterator<E> {
/**
* The modCount value that the iterator believes that the backing
* Queue should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
private int expectedModCount = modCount;
public E next() {
if(expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
Now, why is this code here? If you look at the Javadoc for ConcurrentModificationException you will find that the behaviour of an iterator is undefined if modification occurs to the underlying collection before iteration completes. As such, many of the collections implement this modCount mechanism.
To fix your code
You need to ensure that you don't modify the code mid-loop. If your code is single threaded (as it appears to be) then you can simply do as your coworker suggested and copy it into a list for later inclusion. Also, the use of the Iterator.remove() method is documented to prevent ConcurrentModificationExceptions. An example:
List<Entry> toAdd = new ArrayList<Entry>();
Iterator it = mEntries.iterator();
while(it.hasNext()) {
Entry e = it.next();
if(e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
it.remove();
toAdd.add(newEntry);
}
}
mEntries.addAll(toAdd);
The Javadoc for PriorityQueue says explicitly:
"Note that this implementation is not synchronized. Multiple threads should not access a PriorityQueue instance concurrently if any of the threads modifies the list structurally. Instead, use the thread-safe PriorityBlockingQueue class."
This seems to be your case.
What's wrong in your code was already explained -- implementing iterator, which can consistently iterate through collection with intersected modification is rather hard task to do. You need to specify how to deal with removed items (will it be seen through iterator?), added items, modified items... Even if you can do it consistently it will be rather complex and unefficient implementation -- and, mostly, not very usefull, since use case "iterate without modifications" is much more common. So, java architects choose to deny modification while iterate, and most collections from Java collection API follow this, and throw ConcurrentModificationException if such modification detected.
As for your code -- for me, your just should not make items immutable. Immutability is great thing, but it should not be overused. If Entry object you use here is some kind of domain object, and you really want them to be immutable -- you can just create some kind of temporary data holder (MutableEntry) object, use it inside your algorithm, and copy data to Entry before return. From my point of view it will be best solution.
a slightly better implementation is
List<Entry> toAdd = new ArrayList<Entry>();
for (Iterator<Entry> it= mEntries.iterator();it.hasNext();) {
Entry e = it.next();
if (e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
it.remove();
toAdd.add(newEntry);
}
}
mEntries.addAll(toAdd);
this uses the remove of the iterator and a bulk add afterwards
The problem: Maintain a bidirectional many-to-one relationship among java objects.
Something like the Google/Commons Collections bidi maps, but I want to allow duplicate values on the forward side, and have sets of the forward keys as the reverse side values.
Used something like this:
// maintaining disjoint areas on a gameboard. Location is a space on the
// gameboard; Regions refer to disjoint collections of Locations.
MagicalManyToOneMap<Location, Region> forward = // the game universe
Map<Region, <Set<Location>>> inverse = forward.getInverse(); // live, not a copy
Location parkplace = Game.chooseSomeLocation(...);
Region mine = forward.get(parkplace); // assume !null; should be O(log n)
Region other = Game.getSomeOtherRegion(...);
// moving a Location from one Region to another:
forward.put(parkplace, other);
// or equivalently:
inverse.get(other).add(parkplace); // should also be O(log n) or so
// expected consistency:
assert ! inverse.get(mine).contains(parkplace);
assert forward.get(parkplace) == other;
// and this should be fast, not iterate every possible location just to filter for mine:
for (Location l : mine) { /* do something clever */ }
The simple java approaches are: 1. To maintain only one side of the relationship, either as a Map<Location, Region> or a Map<Region, Set<Location>>, and collect the inverse relationship by iteration when needed; Or, 2. To make a wrapper that maintains both sides' Maps, and intercept all mutating calls to keep both sides in sync.
1 is O(n) instead of O(log n), which is becoming a problem. I started in on 2 and was in the weeds straightaway. (Know how many different ways there are to alter a Map entry?)
This is almost trivial in the sql world (Location table gets an indexed RegionID column). Is there something obvious I'm missing that makes it trivial for normal objects?
I might misunderstand your model, but if your Location and Region have correct equals() and hashCode() implemented, then the set of Location -> Region is just a classical simple Map implementation (multiple distinct keys can point to the same object value). The Region -> Set of Location is a Multimap (available in Google Coll.). You could compose your own class with the proper add/remove methods to manipulate both submaps.
Maybe an overkill, but you could also use in-memory sql server (HSQLDB, etc). It allows you to create index on many columns.
I think you could achieve what you need with the following two classes. While it does involve two maps, they are not exposed to the outside world, so there shouldn't be a way for them to get out of sync. As for storing the same "fact" twice, I don't think you'll get around that in any efficient implementation, whether the fact is stored twice explicitly as it is here, or implicitly as it would be when your database creates an index to make joins more efficient on your 2 tables. you can add new things to the magicset and it will update both mappings, or you can add things to the magicmapper, which will then update the inverse map auotmatically. The girlfriend is calling me to bed now so I cannot run this through a compiler - it should be enough to get you started. what puzzle are you trying to solve?
public class MagicSet<L> {
private Map<L,R> forward;
private R r;
private Set<L> set;
public MagicSet<L>(Map forward, R r) {
this.forward = map;
this.r = r;
this.set = new HashSet<L>();
}
public void add(L l) {
set.add(l);
forward.put(l,r);
}
public void remove(L l) {
set.remove(l);
forward.remove(l);
}
public int size() {
return set.size();
}
public in contains(L l){
return set.contains(l);
}
// caution, do not use the remove method from this iterator. if this class was going
// to be reused often you would want to return a wrapped iterator that handled the remove method properly. In fact, if you did that, i think you could then extend AbstractSet and MagicSet would then fully implement java.util.Set.
public Iterator iterator() {
return set.iterator();
}
}
public class MagicMapper<L,R> { // note that it doesn't implement Map, though it could with some extra work. I don't get the impression you need that though.
private Map<L,R> forward;
private Map<R,MagicSet<L>> inverse;
public MagicMapper<L,R>() {
forward = new HashMap<L,R>;
inverse = new HashMap<R,<MagicSet<L>>;
}
public R getForward(L key) {
return forward.get(key);
}
public Set<L> getBackward(R key) {
return inverse.get(key); // this assumes you want a null if
// you try to use a key that has no mapping. otherwise you'd return a blank MagicSet
}
public void put (L l, R r) {
R oldVal = forward.get(l);
// if the L had already belonged to an R, we need to undo that mapping
MagicSet<L> oldSet = inverse.get(oldVal);
if (oldSet != null) {oldSet.remove(l);}
// now get the set the R belongs to, and add it.
MagicSet<L> newSet = inverse.get(l);
if (newSet == null) {
newSet = new MagicSet<L>(forward, r);
inverse.put(r,newSet);
}
newSet.add(l); // magically updates the "forward" map
}
}