Fine grained control of the frame loop using play controls
After completing https://github.com/trytriplex/triplex/issues/17 we implemented a bare bones implementation that runs the scene, plays through the default camera, turns on raycasting and userland controls. What wasn't implemented was proper support for controlling the frame loop, meaning when in edit mode the frame loop is stopped and so on.
When fixing the scroll controls issue and discussing options with @krispya he proposed exploring an event hijacking solution where all user land events are suppressed when the editor is in edit / pause modes.
We'd need to explore a method of whitelisting events we want to allow but this sounds promising.
This should block all user land events from:
- add event listener
- request animation frame
- set timeout
- set interval
Here is the Claude code to get started with:
// Store original methods
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
// Create a WeakMap to store listeners
const listenerMap = new WeakMap();
// Override addEventListener
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (!listenerMap.has(this)) {
listenerMap.set(this, {});
}
if (!listenerMap.get(this)[type]) {
listenerMap.get(this)[type] = [];
}
listenerMap.get(this)[type].push({ listener, options });
return originalAddEventListener.call(this, type, listener, options);
};
// Override removeEventListener
EventTarget.prototype.removeEventListener = function(type, listener, options) {
const listeners = listenerMap.get(this);
if (listeners && listeners[type]) {
const index = listeners[type].findIndex(l => l.listener === listener);
if (index !== -1) {
listeners[type].splice(index, 1);
}
}
return originalRemoveEventListener.call(this, type, listener, options);
};
// Function to remove all listeners
function removeAllListeners(element, type) {
const listeners = listenerMap.get(element);
if (listeners && listeners[type]) {
listeners[type].forEach(({ listener, options }) => {
originalRemoveEventListener.call(element, type, listener, options);
});
listeners[type] = [];
}
}
// Function to restore all listeners
function restoreAllListeners(element, type) {
const listeners = listenerMap.get(element);
if (listeners && listeners[type]) {
listeners[type].forEach(({ listener, options }) => {
originalAddEventListener.call(element, type, listener, options);
});
}
}
I'm not sure this is something people actually want. Would love any comments from the community on this.
For now I'm dropping this as low priority — I'm not sure if there are legitimate needs to implement this that are already covered by alternate implementations.
If you think strongly about needing this please raise another issue with your use case and let's talk!