Reset value of dat.gui controller without setting off onChange function

I am currently working on a project that requires the use of dat.gui.

An issue I am running into is that I want to reset the value of certain controllers when certain controllers are activated. The issue is that when I reset the value of the controller, it sets off the onChange event of those controllers, causing an infinite loop of resetting all of my controllers.

Here is the example pen where the problem has been isolated: https://codepen.io/michaelnicol/pen/jOLxaOB?editors=1011

import dat from "https://cdn.skypack.dev/dat.gui";
const world = {
  mazeIndex: 0,
  startZ: 0,
  startY: 0,
  startX: 0,
  endZ: 0,
  endY: 0,
  endX: 0
};
const gui = new dat.GUI();
 let timesdone = 0;
     function resetGUIControllers(fullReset = true, mazeIndexReset = true) {
       // fullReset --> Resets all controllers. This is default parameter.
       // mazeIndexResert = true --> only resets mazeIndex. Otherwise false, it only resets coords.
        gui.__controllers.map(c => {
          timesdone++
          console.log(timesdone)
          const { property } = c
          // false || true
          if (fullReset || mazeIndexReset) {
            console.log("mazeIndexReset")
            // .onChange(generateMazeIndexMaze)
            c.setValue(c.initialValue).min(0).max(sortedTracedMazes.length - 1);
          }
          // false or !true
          if (fullReset || !mazeIndexReset) {
            // .onChange(generateCoordMaze
            console.log("Coord reset")
          c.setValue(c.initialValue).min(0)
          switch (property) {
          case "startZ":
          c.max(5)
          break;
          case "startY":
          c.max(6)
          break;
          case "startX":
          c.max(7)
          break;
          case "endZ":
          c.max(5)
          break;
          case "endY":
          c.max(6)
          break;
          case "endX":
          c.max(7)
          break;
         }
        }
          return c;
      });
    }
 function setGUIControllers() {
         //  .listen() was used before after onChange --> onChange(function).listen(). 
        // I removed it but it didn't fix the issue 
        gui.add(world, "mazeIndex", 0,  15).onChange(generateMazeIndexMaze) 
        gui.add(world, "startZ", 0, 5).onChange(generateCoordMaze) 
        gui.add(world, "startY", 0, 6).onChange(generateCoordMaze) 
        gui.add(world, "startX", 0, 7).onChange(generateCoordMaze) 
        gui.add(world, "endZ", 0, 5).onChange(generateCoordMaze) 
        gui.add(world, "endY", 0, 6).onChange(generateCoordMaze) 
        gui.add(world, "endX", 0, 7).onChange(generateCoordMaze) 
      }
     function runGUIControllers() {
     if (gui.__controllers.length == 0) {
          setGUIControllers()
       } else {
          resetGUIControllers(true)
       }
     }
       function generateCoordMaze() {
        const {startZ, startY, startX, endZ, endY, endX} = world;
        [startZ, startY, startX, endZ, endY, endX].map(c => c = Math.floor(c))
        console.log([startZ, startY, startX, endZ, endY, endX])
        let index = 0;
         resetGUIControllers(false, true)
         // find right maze
         // call model maze with that index and maze
       }
       function generateMazeIndexMaze() {
        let i = Math.floor(world.mazeIndex);
        resetGUIControllers(false, false)
        console.log("Generate generateMazeIndexMaze Called: " + i);
      }
runGUIControllers();

When the pen first starts up, the pen calls runGUIControllers(); on line 86 (function is on line 65) in order to set up the dat.gui controllers.

The idea is that when I use the mazeIndex slider, it should activate the generateMazeIndexMaze() onChange function, return the value of the slider from the world object, and then call the resetGUIControllers(false, false) (Line 83). By calling this reset function with two false arguments, it should reset all of the startZ, startY, startX, endZ, endY, and endX controllers without resetting mazeIndex.

The reset function itself is just maps the gui.__controllers array and resets the controllers with the requested name (property).

The same should be true with the start and end controllers. Whenever one of them is used, it should call the generateCoordMaze() onChange function, retrieve the values of all of the start/ end controllers from the world object, and then call the resetGUIControllers(false, true) function. This should reset the mazeIndex controller without resetting the start/ end controllers.

The issue is that this is not happening the way I intend. For example, whenever the mazeIndex slider is used, it resets the start/ end controllers. This activates their onChange function to reset the mazeIndex slider, which then in return starts an infinite loop (I am assuming).

I hope this explanation made sense. Any ideas on how to get around this?

I think setting the world object properties and then calling updateDisplay on each of the controllers might be a better option.

As an aside, accessing properties on an API that starts with __ is not a good idea. They are internal for a reason (relying on them breaks the API contract). The devs can make breaking changes to the internal part of the API and still keep the public-facing API backward compatible. .add() returns the controller so you can save them to your own controller object instead. It remembers the min/max so you do not need to reset them.

Anyway, I’m not sure I understand the logic you are going for. I will also point out that using multiple Boolean values for a function as you are can get confusing surprisingly fast. I would suggest using an options object instead. It makes it a lot more clear what is happening both at the call site and inside the function that is using the options.

As said, I’m not 100% sure what you are trying to do but here is an example of the top slider resetting the other sliders and the other sliders resetting the top slider.

1 Like

Thank you for the help, this is exactly what I am looking for. I never really thought of using objects for my arguments, and it makes much more sense. I also will make sure to make sure that I don’t edit internal objects.

I would’ve never thought of saving the controller from the .add(). How were you able to find this information?

A lot of libraries you have used use an options object, I’m sure you have seen it many times but you may not have given it much thought > javascript options object pattern.

Just by reading the API documentation gui.add

1 Like