<commit_before>package ethanjones.cubes.graphics.rendering;

import ethanjones.cubes.core.performance.Performance;
import ethanjones.cubes.core.performance.PerformanceTags;
import ethanjones.cubes.core.settings.Settings;
import ethanjones.cubes.core.system.Pools;
import ethanjones.cubes.entity.Entity;
import ethanjones.cubes.graphics.Graphics;
import ethanjones.cubes.graphics.world.*;
import ethanjones.cubes.input.CameraController;
import ethanjones.cubes.side.common.Cubes;
import ethanjones.cubes.world.CoordinateConverter;
import ethanjones.cubes.world.World;
import ethanjones.cubes.world.reference.AreaReference;
import ethanjones.cubes.world.storage.Area;
import ethanjones.cubes.world.storage.AreaMap;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.RenderableProvider;
import com.badlogic.gdx.math.Frustum;
import com.badlogic.gdx.utils.Disposable;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

import static ethanjones.cubes.graphics.Graphics.modelBatch;

public class WorldRenderer implements Disposable {

  private static ArrayDeque<AreaNode> poolNode = new ArrayDeque<AreaNode>();

  public PerspectiveCamera camera;
  private ArrayList<AreaRenderer> needToRefresh = new ArrayList<AreaRenderer>();
  private ArrayDeque<AreaNode> queue = new ArrayDeque<AreaNode>();
  private HashSet<AreaNode> checkedNodes = new HashSet<AreaNode>();

  public WorldRenderer() {
    camera = new PerspectiveCamera(Settings.getIntegerSettingValue(Settings.GRAPHICS_FOV), Graphics.RENDER_WIDTH, Graphics.RENDER_HEIGHT) {
      @Override
      public void update(boolean b) {
        viewportWidth = Graphics.RENDER_WIDTH;
        viewportHeight = Graphics.RENDER_HEIGHT;
        super.update(b);
      }
    };
    camera.near = 0.1f;
    camera.far = 768f;

    Cubes.getClient().inputChain.cameraController = new CameraController(camera);
  }
  
  public void render() {
    WorldGraphicsPools.free();

    Performance.start(PerformanceTags.CLIENT_RENDER_WORLD);
    AreaRenderer.frameStart();

    needToRefresh.clear();
    queue.clear();
    checkedNodes.clear();
    
    modelBatch.begin(camera);

    int renderDistance = Settings.getIntegerSettingValue(Settings.GRAPHICS_VIEW_DISTANCE);
    AreaBoundaries.update();

    Performance.start(PerformanceTags.CLIENT_RENDER_WORLD_AREAS);
    World world = Cubes.getClient().world;
    AreaMap areaMap = world.map;
    areaMap.lock.readLock();

    AreaReference pos = Pools.obtainAreaReference().setFromPositionVector3(Cubes.getClient().player.position);
    int yPos = CoordinateConverter.area(Cubes.getClient().player.position.y);

    AreaNode startingNode = get(areaMap.getArea(pos.areaX, pos.areaZ), pos.areaX, pos.areaZ, yPos);
    startingNode.firstNode = true; // Fix flashes caused by the starting area just being in frustum as flying up/down (e.g. Y=64.001) and areaInFrustum returning false
    queue.add(startingNode);

    while (!queue.isEmpty()) {
      AreaNode node = queue.pop();
      Area area = node.area;
      int ySection = node.ySection;
      int areaX = node.areaX;
      int areaZ = node.areaZ;

      if (!checkedNodes.add(node)) {
        poolNode.add(node);
        continue;
      }

      if (!node.firstNode) {
        if (!areaInFrustum(areaX, areaZ, ySection, camera.frustum)) continue;
        if (!inRange(areaX, areaZ, pos.areaX, pos.areaZ, renderDistance)) continue;
      }

      boolean nullArea = area == null || ySection >= area.height;
      int traverse = 0;

      if (!nullArea && ySection >= 0) {
        area.lock.writeLock();

        int status = area.renderStatus[ySection];
        if (status == AreaRenderStatus.UNKNOWN) status = AreaRenderStatus.update(area, ySection);
        traverse = status == AreaRenderStatus.EMPTY ? 0 : status;
        boolean render = status != AreaRenderStatus.EMPTY && !complete(area, ySection, areaMap, status);

        if (render) {
          if (area.areaRenderer[ySection] == null) Pools.obtain(AreaRenderer.class).set(area, ySection);

          AreaRenderer areaRenderer = area.areaRenderer[ySection];
          if (areaRenderer.needsRefresh()) {
            needToRefresh.add(areaRenderer);
          } else {
            modelBatch.render(areaRenderer);
            renderIfNotNull(AreaBoundaries.drawArea(areaX, ySection, areaZ));
          }
        } else if (area.areaRenderer[ySection] != null) {
          AreaRenderer.free(area.areaRenderer[ySection]);
          area.areaRenderer[ySection] = null;
        }

        area.lock.writeUnlock();
      }

      if (traverse != AreaRenderStatus.COMPLETE) {
        Area a;
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_X) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX + 1, areaZ)) {
          if ((a = areaMap.lockedGetArea(areaX + 1, areaZ)) != null) queue.add(get(a, areaX + 1, areaZ, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_X) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX - 1, areaZ)) {
          if ((a = areaMap.lockedGetArea(areaX - 1, areaZ)) != null) queue.add(get(a, areaX - 1, areaZ, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_Z) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX, areaZ + 1)) {
          if ((a = areaMap.lockedGetArea(areaX, areaZ + 1)) != null) queue.add(get(a, areaX, areaZ + 1, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_Z) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX, areaZ - 1)) {
          if ((a = areaMap.lockedGetArea(areaX, areaZ - 1)) != null) queue.add(get(a, areaX, areaZ - 1, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_Y) == 0 && !nullArea && ySection >= yPos) {
          queue.add(get(area, areaX, areaZ, ySection + 1));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_Y) == 0 && ySection > 0 && ySection <= yPos) {
          queue.add(get(area, areaX, areaZ, ySection - 1));
        }
      }
    }
    areaMap.lock.readUnlock();
    poolNode.addAll(checkedNodes);
    Performance.stop(PerformanceTags.CLIENT_RENDER_WORLD_AREAS);

    if (needToRefresh.size() > 0) {
      Performance.start(PerformanceTags.CLIENT_RENDER_WORLD_UPDATES);
      Collections.sort(needToRefresh, new AreaRendererSorter());<commit_msg>Improve WorldRenderer to use IntSet rather than HashSet to prevent HashMap node allocations<commit_after>package ethanjones.cubes.graphics.rendering;

import ethanjones.cubes.core.performance.Performance;
import ethanjones.cubes.core.performance.PerformanceTags;
import ethanjones.cubes.core.settings.Settings;
import ethanjones.cubes.core.system.Pools;
import ethanjones.cubes.entity.Entity;
import ethanjones.cubes.graphics.Graphics;
import ethanjones.cubes.graphics.world.*;
import ethanjones.cubes.input.CameraController;
import ethanjones.cubes.side.common.Cubes;
import ethanjones.cubes.world.CoordinateConverter;
import ethanjones.cubes.world.World;
import ethanjones.cubes.world.reference.AreaReference;
import ethanjones.cubes.world.storage.Area;
import ethanjones.cubes.world.storage.AreaMap;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.RenderableProvider;
import com.badlogic.gdx.math.Frustum;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.IntSet;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;

import static ethanjones.cubes.graphics.Graphics.modelBatch;

public class WorldRenderer implements Disposable {

  private static ArrayDeque<AreaNode> poolNode = new ArrayDeque<AreaNode>(256);

  public PerspectiveCamera camera;
  private ArrayList<AreaRenderer> needToRefresh = new ArrayList<AreaRenderer>();
  private ArrayDeque<AreaNode> queue = new ArrayDeque<AreaNode>();
  private IntSet checkedNodes = new IntSet(1024);

  public WorldRenderer() {
    camera = new PerspectiveCamera(Settings.getIntegerSettingValue(Settings.GRAPHICS_FOV), Graphics.RENDER_WIDTH, Graphics.RENDER_HEIGHT) {
      @Override
      public void update(boolean b) {
        viewportWidth = Graphics.RENDER_WIDTH;
        viewportHeight = Graphics.RENDER_HEIGHT;
        super.update(b);
      }
    };
    camera.near = 0.1f;
    camera.far = 768f;

    Cubes.getClient().inputChain.cameraController = new CameraController(camera);
  }
  
  public void render() {
    WorldGraphicsPools.free();

    Performance.start(PerformanceTags.CLIENT_RENDER_WORLD);
    AreaRenderer.frameStart();

    needToRefresh.clear();
    queue.clear();
    checkedNodes.clear();
    
    modelBatch.begin(camera);

    int renderDistance = Settings.getIntegerSettingValue(Settings.GRAPHICS_VIEW_DISTANCE);
    AreaBoundaries.update();

    Performance.start(PerformanceTags.CLIENT_RENDER_WORLD_AREAS);
    World world = Cubes.getClient().world;
    AreaMap areaMap = world.map;
    areaMap.lock.readLock();

    AreaReference pos = Pools.obtainAreaReference().setFromPositionVector3(Cubes.getClient().player.position);
    int yPos = CoordinateConverter.area(Cubes.getClient().player.position.y);

    AreaNode startingNode = get(areaMap.getArea(pos.areaX, pos.areaZ), pos.areaX, pos.areaZ, yPos);
    startingNode.firstNode = true; // Fix flashes caused by the starting area just being in frustum as flying up/down (e.g. Y=64.001) and areaInFrustum returning false
    queue.add(startingNode);

    while (!queue.isEmpty()) {
      AreaNode node = queue.pop();
      Area area = node.area;
      int ySection = node.ySection;
      int areaX = node.areaX;
      int areaZ = node.areaZ;
      int packedID = (areaX & 0x3FF) | ((areaZ & 0x3FF) << 10) | ((ySection & 0x3FF) << 20);

      if (!checkedNodes.add(packedID) || (!node.firstNode && (!areaInFrustum(areaX, areaZ, ySection, camera.frustum) || !inRange(areaX, areaZ, pos.areaX, pos.areaZ, renderDistance)))) {
        poolNode.add(node);
        continue;
      }

      boolean nullArea = area == null || ySection >= area.height;
      int traverse = 0;

      if (!nullArea && ySection >= 0) {
        area.lock.writeLock();

        int status = area.renderStatus[ySection];
        if (status == AreaRenderStatus.UNKNOWN) status = AreaRenderStatus.update(area, ySection);
        traverse = status == AreaRenderStatus.EMPTY ? 0 : status;
        boolean render = status != AreaRenderStatus.EMPTY && !complete(area, ySection, areaMap, status);

        if (render) {
          if (area.areaRenderer[ySection] == null) Pools.obtain(AreaRenderer.class).set(area, ySection);

          AreaRenderer areaRenderer = area.areaRenderer[ySection];
          if (areaRenderer.needsRefresh()) {
            needToRefresh.add(areaRenderer);
          } else {
            modelBatch.render(areaRenderer);
            renderIfNotNull(AreaBoundaries.drawArea(areaX, ySection, areaZ));
          }
        } else if (area.areaRenderer[ySection] != null) {
          AreaRenderer.free(area.areaRenderer[ySection]);
          area.areaRenderer[ySection] = null;
        }

        area.lock.writeUnlock();
      }

      if (traverse != AreaRenderStatus.COMPLETE) {
        Area a;
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_X) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX + 1, areaZ)) {
          if ((a = areaMap.lockedGetArea(areaX + 1, areaZ)) != null) queue.add(get(a, areaX + 1, areaZ, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_X) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX - 1, areaZ)) {
          if ((a = areaMap.lockedGetArea(areaX - 1, areaZ)) != null) queue.add(get(a, areaX - 1, areaZ, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_Z) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX, areaZ + 1)) {
          if ((a = areaMap.lockedGetArea(areaX, areaZ + 1)) != null) queue.add(get(a, areaX, areaZ + 1, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_Z) == 0 && further(pos.areaX, pos.areaZ, areaX, areaZ, areaX, areaZ - 1)) {
          if ((a = areaMap.lockedGetArea(areaX, areaZ - 1)) != null) queue.add(get(a, areaX, areaZ - 1, ySection));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MAX_Y) == 0 && !nullArea && ySection >= yPos) {
          queue.add(get(area, areaX, areaZ, ySection + 1));
        }
        if ((traverse & AreaRenderStatus.COMPLETE_MIN_Y) == 0 && ySection > 0 && ySection <= yPos) {
          queue.add(get(area, areaX, areaZ, ySection - 1));
        }
      }
      poolNode.add(node);
    }
    areaMap.lock.readUnlock();
    Performance.stop(PerformanceTags.CLIENT_RENDER_WORLD_AREAS);

    if (needToRefresh.size() > 0) {
      Performance.start(PerformanceTags.CLIENT_RENDER_WORLD_UPDATES);
      Collections.sort(needToRefresh, new AreaRendererSorter());
{'fraction_non_alphanumeric': 0.12352036894696387, 'fraction_numerical': 0.007148347425057648, 'mean_word_length': 3.555672268907563, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '827704', 'n_tokens_mistral': 4301, 'n_tokens_neox': 4129, 'n_words': 796}