gt:         String sfov = Float.toString(fov);
context: /*
   * This file is part of Gaia Sky, which is released under the Mozilla Public License 2.0.
   * See the file LICENSE.md in the project root for full license details.
   */
  
  package gaiasky.util;
  
  import com.badlogic.gdx.net.HttpStatus;
  import gaiasky.event.EventManager;
  import gaiasky.event.Events;
  import gaiasky.event.IObserver;
  import gaiasky.render.ComponentTypes.ComponentType;
  import gaiasky.util.Logger.Log;
  import gaiasky.util.math.MathUtilsd;
  import gaiasky.util.math.Vector3d;
  import gaiasky.util.time.ITimeFrameProvider;
  
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.http.HttpResponse.BodyHandler;
  import java.net.http.HttpResponse.BodySubscriber;
  import java.util.List;
  import java.util.function.Function;
  
  /**
   * Manages a master instance which makes available state information to others
   * in order to synchronize a session.
   *
   * @author tsagrista
   */
  public class MasterManager implements IObserver {
      private static final Log logger = Logger.getLogger(MasterManager.class);
  
      /** Will attempt reconnection to offline slaves with this frequency **/
      private static final long RECONNECT_TIME_MS = 5000;
  
      // Singleton instance
      public static MasterManager instance;
  
      public static void initialize() {
          if (GlobalConf.program.NET_MASTER)
              MasterManager.instance = new MasterManager();
      }
  
      public static boolean hasSlaves() {
          return instance != null && instance.slaves != null && !instance.slaves.isEmpty();
      }
  
      // Slave list
      private List<String> slaves;
  
      public boolean isSlaveConnected(String slaveName) {
          return isSlaveConnected(getSlaveIndex(slaveName));
  
      }
  
      public int getSlaveIndex(String slaveName) {
          return slaves.indexOf(slaveName);
      }
  
      public boolean isSlaveConnected(int index) {
          if (index >= 0 && index < slaves.size()) {
              return slaveStates[index] == 0;
          }
          return false;
      }
  
      public byte[] getSlaveStates() {
          return slaveStates;
      }
  
      /**
       * Vector with slave states
       * <ul>
       * <li>0 - ok</li>
       * <li>-1 - error</li>
       * <li>1 - retrying</li>
       * </ul>
       */
      private byte[] slaveStates;
      /** Slave connection attempt flags **/
      private byte[] slaveFlags;
      /** Last ping times for each slave **/
      private long[] slavePingTimes;
  
      // Handlers
      private ResponseHandler[] responseHandlers;
      private ExceptHandler[] exceptHandlers;
  
      // HTTP client
      private HttpClient http;
  
      private MasterManager() {
          super();
  
          // Slave objects
          slaves = GlobalConf.program.NET_MASTER_SLAVES;
          if (slaves != null && slaves.size() > 0) {
              slaveStates = new byte[slaves.size()];
              slaveFlags = new byte[slaves.size()];
              slavePingTimes = new long[slaves.size()];
              for (int i = 0; i < slaveStates.length; i++) {
                  slaveStates[i] = 0;
                  slaveFlags[i] = 0;
                  slavePingTimes[i] = 0l;
              }
          }
  
          if (slaves != null && slaves.size() > 0) {
              responseHandlers = new ResponseHandler[slaves.size()];
              exceptHandlers = new ExceptHandler[slaves.size()];
              for (int i = 0; i < slaveStates.length; i++) {
                  responseHandlers[i] = new ResponseHandler(i);
                  exceptHandlers[i] = new ExceptHandler(i);
              }
          }
  
          // Initialize http client
          http = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
  
          // Subscribe to events that need to be broadcasted
          EventManager.instance.subscribe(this, Events.FOV_CHANGED_CMD, Events.TOGGLE_VISIBILITY_CMD, Events.STAR_BRIGHTNESS_CMD, Events.STAR_MIN_OPACITY_CMD, Events.STAR_POINT_SIZE_CMD, Events.DISPOSE);
      }
  
      /**
       * Broadcasts the given camera state and time to all the slaves
       *
       * @param pos  Camera position
       * @param dir  Camera direction
       * @param up   Camera up
       * @param time Current time
       */
      public void boardcastCameraAndTime(Vector3d pos, Vector3d dir, Vector3d up, ITimeFrameProvider time) {
          String spos = TextUtils.surround(pos.toString(), "[", "]");
          String sdir = TextUtils.surround(dir.toString(), "[", "]");
          String sup = TextUtils.surround(up.toString(), "[", "]");
          String stime = Long.toString(time.getTime().toEpochMilli());
  
          boolean slaveOffline = false;
          int i = 0;
          for (String slave : slaves) {
              if (slaveStates[i] == 0 || slaveFlags[i] == 1) {
                  HttpRequest req = HttpRequest.newBuilder().uri(URI.create(slave + "setCameraStateAndTime?arg0=" + spos + "&arg1=" + sdir + "&arg2=" + sup + "&arg3=" + stime)).GET().
                          build();
  
                  try {
                      http.sendAsync(req, rhandler(i)).thenApply(HttpResponse::body).exceptionally(ehandler(i));
                  }catch(Exception e){
                      logger.error(e);
                  }
                  if(slaveFlags[i] == 1)
                      slaveFlags[i] = 0;
              } else {
                  slaveOffline = slaveStates[i] == -1;
              }
              i++;
          }
  
          // Retry connections after RECONNECT_TIME_MS milliseconds
          if (slaveOffline) {
              long now = System.currentTimeMillis();
              for (i = 0; i < slaveFlags.length; i++) {
                  if (slaveStates[i] < 0 && now - slavePingTimes[i] > RECONNECT_TIME_MS) {
                      slaveFlags[i] = 1;
                  }
              }
  
          }
  
      }
  
      /**
       * Broadcasts the given camera state to all the slaves
       *
       * @param pos Camera position
       * @param dir Camera direction
       * @param up  Camera up
       */
      public void boardcastCamera(Vector3d pos, Vector3d dir, Vector3d up) {
          String spos = TextUtils.surround(pos.toString(), "[", "]");
          String sdir = TextUtils.surround(dir.toString(), "[", "]");
          String sup = TextUtils.surround(up.toString(), "[", "]");
  
          int i = 0;
          for (String slave : slaves) {
              if (slaveStates[i] == 0) {
                  HttpRequest req = HttpRequest.newBuilder().uri(URI.create(slave + "setCameraState?arg0=" + spos + "&arg1=" + sdir + "&arg2=" + sup)).GET().
                          build();
                  http.sendAsync(req, rhandler(i)).thenApply(HttpResponse::body).exceptionally(ehandler(i));
              }
              i++;
          }
      }
  
      public void setSlaveYaw(String slave, float yaw) {
          String syaw = Float.toString(yaw);
          if (isSlaveConnected(slave)) {
              int i = getSlaveIndex(slave);
              HttpRequest req = HttpRequest.newBuilder().uri(URI.create(slave + "setProjectionYaw?arg0=" + syaw)).GET().
                      build();
              http.sendAsync(req, rhandler(i)).thenApply(HttpResponse::body).exceptionally(ehandler(i));
          }
      }
  
      public void setSlavePitch(String slave, float pitch) {
          String spitch = Float.toString(pitch);
          if (isSlaveConnected(slave)) {
              int i = getSlaveIndex(slave);
              HttpRequest req = HttpRequest.newBuilder().uri(URI.create(slave + "setProjectionPitch?arg0=" + spitch)).GET().
                      build();
              http.sendAsync(req, rhandler(i)).thenApply(HttpResponse::body).exceptionally(ehandler(i));
          }
      }
  
      public void setSlaveRoll(String slave, float roll) {
          String sroll = Float.toString(roll);
          if (isSlaveConnected(slave)) {
              int i = getSlaveIndex(slave);
              HttpRequest req = HttpRequest.newBuilder().uri(URI.create(slave + "setProjectionRoll?arg0=" + sroll)).GET().
                      build();
              http.sendAsync(req, rhandler(i)).thenApply(HttpResponse::body).exceptionally(ehandler(i));
          }
      }
  
      public void setSlaveFov(String slave, float fov) {
  