Question about PhysicsCollisionListener

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.

1 Like

I doubt synchronization on the ContactManager will ever be a measurable performance overhead.

And if you’re trying to squeeze every drop of power out of Minie, you’ll probably wind up using PARALLEL threaded mode anyway. That’s what it’s for.

However, it’s Thanksgiving, and I’m not in the mood to argue. Here you go: