Now that I think about it, I think customization of the ContactManager
class is necessary. The current implementation uses the synchronized methods . Where is the problem?
Performance Overhead : Synchronization can increase the overhead of a program, as it requires the threads to wait for access to the shared resource, leading to a decrease in overall performance.
If I set BulletAppState with ThreadingType.SEQUENCE (which is the default value) there is no need for the ContactManager.distributeEvents()
method to be synchronized. So my implementation of the ContactManager class will not have any synchronized methods. Suppose I also want simpler flag handling, because I can do without checking the listenersâ bitmasks before notifying each event. Here is what my implementation will look like.
â ContactManager
public abstract class ContactManager implements ContactListener {
// PhysicsSpace whose notifications are being managed.
private final PhysicsSpace space;
public ContactManager(PhysicsSpace space) {
this.space = space;
}
public abstract void addCollisionListener(PhysicsCollisionListener listener);
public abstract void addOngoingCollisionListener(PhysicsCollisionListener listener);
public abstract void addContactListener(ContactListener listener, boolean doEnded, boolean doProcessed, boolean doStarted);
public abstract void removeCollisionListener(PhysicsCollisionListener listener);
public abstract void removeOngoingCollisionListener(PhysicsCollisionListener listener);
public abstract void removeContactListener(ContactListener listener);
public abstract int countCollisionListeners();
public abstract void distributeEvents();
public abstract void update(float timeInterval, int maxSteps);
public PhysicsSpace getPhysicsSpace() {
return space;
}
}
âContactManagerSingleThread
public class ContactManagerSingleThread extends ContactManager {
@Getter @Setter
private boolean doEnded = true;
@Getter @Setter
private boolean doProcessed = true;
@Getter @Setter
private boolean doStarted = true;
private final Deque<PhysicsCollisionEvent> ongoingEvents = new ArrayDeque<>(99);
private final Deque<PhysicsCollisionEvent> startedEvents = new ArrayDeque<>(99);
private final Collection<PhysicsCollisionListener> ongoingListeners = new ArrayList<>(4);
private final Collection<PhysicsCollisionListener> startedListeners = new ArrayList<>(4);
private final List<ContactListener> immediateListeners = new ArrayList<>(4);
public ContactManagerSingleThread(PhysicsSpace space) {
super(space);
}
@Override
public void onContactEnded(long manifoldId) {
for (ContactListener listener : immediateListeners) {
listener.onContactEnded(manifoldId);
}
}
@Override
public void onContactProcessed(PhysicsCollisionObject pcoA, PhysicsCollisionObject pcoB, long pointId) {
for (ContactListener listener : immediateListeners) {
listener.onContactProcessed(pcoA, pcoB, pointId);
}
if (!ongoingListeners.isEmpty()) {
PhysicsCollisionEvent event = new PhysicsCollisionEvent(pcoA, pcoB, pointId);
// Queue the event to be handled later by distributeEvents().
ongoingEvents.add(event);
}
}
@Override
public void onContactStarted(long manifoldId) {
for (ContactListener listener : immediateListeners) {
listener.onContactStarted(manifoldId);
}
if (!startedListeners.isEmpty()) {
// ...
// Maybe I could put a pool of reusable PhysicsCollisionEvent objects here, I don't know.
for (int i = 0; i < numPoints; ++i) {
long pointId = PersistentManifolds.getPointId(manifoldId, i);
PhysicsCollisionEvent event = new PhysicsCollisionEvent(pcoA, pcoB, pointId);
// Queue the event to be handled later by distributeEvents().
startedEvents.add(event);
}
}
}
@Override
public void addCollisionListener(PhysicsCollisionListener listener) {
startedListeners.add(listener);
}
@Override
public void addOngoingCollisionListener(PhysicsCollisionListener listener) {
ongoingListeners.add(listener);
}
@Override
public void addContactListener(ContactListener listener, boolean doEnded, boolean doProcessed, boolean doStarted) {
/**
* I believe that this method should not be included in the interface because it
* relies on specific implementation that over-couples listeners and flags with
* no possibility to separate them.
*/
}
public void addContactListener(ContactListener listener) {
immediateListeners.add(listener);
}
@Override
public void removeCollisionListener(PhysicsCollisionListener listener) {
startedListeners.remove(listener);
}
@Override
public void removeOngoingCollisionListener(PhysicsCollisionListener listener) {
ongoingListeners.remove(listener);
}
@Override
public void removeContactListener(ContactListener listener) {
immediateListeners.remove(listener);
}
@Override
public int countCollisionListeners() {
return ongoingListeners.size() + startedListeners.size();
}
/**
* Distribute queued collision events to registered listeners.
*/
@Override
public void distributeEvents() {
while (!startedEvents.isEmpty()) {
PhysicsCollisionEvent event = startedEvents.pop();
for (PhysicsCollisionListener listener : startedListeners) {
listener.collision(event);
}
}
while (!ongoingEvents.isEmpty()) {
PhysicsCollisionEvent event = ongoingEvents.pop();
for (PhysicsCollisionListener listener : ongoingListeners) {
listener.collision(event);
}
}
}
@Override
public void update(float timeInterval, int maxSteps) {
getPhysicsSpace().update(timeInterval, maxSteps, doEnded, doProcessed, doStarted);
}
// ------my extra methods
public void clearContactListeners() {
immediateListeners.clear();
}
public void clearCollisionListeners() {
startedListeners.clear();
}
public void clearOngoingCollisionListeners() {
ongoingListeners.clear();
}
}
This is just one of many possible solutions.
To allow users to squeeze every drop of power out of Minie, I think a way to access the collision management system is important. Each game may have special needs; it is not possible to envision one implementation of ContactManager that fits all. I think maybe some customization freedom is needed in this.
I am interested in learning how to build Minie from source to help with testing. I will give it a try following your instructions as soon as I can.