diff --git a/src/main/java/com/seibel/lod/core/config/gui/ConfigScreen.java b/src/main/java/com/seibel/lod/core/config/gui/ConfigScreen.java index 1cb6c4b1b..ab76c73cb 100644 --- a/src/main/java/com/seibel/lod/core/config/gui/ConfigScreen.java +++ b/src/main/java/com/seibel/lod/core/config/gui/ConfigScreen.java @@ -1,17 +1,99 @@ package com.seibel.lod.core.config.gui; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.render.GLProxy; +import com.seibel.lod.core.render.objects.GLState; +import com.seibel.lod.core.render.objects.GLVertexBuffer; +import com.seibel.lod.core.render.objects.ShaderProgram; +import com.seibel.lod.core.render.objects.VertexAttribute; +import org.lwjgl.opengl.GL32; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * @author coolGi */ public class ConfigScreen extends AbstractScreen { + ShaderProgram basicShader; + GLVertexBuffer sameContextBuffer; + GLVertexBuffer sharedContextBuffer; + VertexAttribute va; + + private static final float[] vertices = { + // PosX,Y, ColorR,G,B,A + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + 0.4f, -0.4f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.3f, 0.3f, 1.0f, 1.0f, 0.0f, 0.0f, + -0.2f, 0.2f, 0.0f, 1.0f, 1.0f, 1.0f + }; + @Override public void init() { System.out.println("init"); + + va = VertexAttribute.create(); + va.bind(); + // Pos + va.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addVec2Pointer(false)); + // Color + va.setVertexAttribute(0, 1, VertexAttribute.VertexPointer.addVec4Pointer(false)); + va.completeAndCheck(Float.BYTES * 6); + basicShader = new ShaderProgram("shaders/test/vert.vert", "shaders/test/frag.frag", + "fragColor", new String[]{"vPosition", "color"}); + createBuffer(); + } + + private void createBuffer() { + GLProxy.getInstance().recordOpenGlCall(() -> sharedContextBuffer = createTextingBuffer()); + GLProxy.ensureAllGLJobCompleted(); + sameContextBuffer = createTextingBuffer(); + } + + private static GLVertexBuffer createTextingBuffer() { + ByteBuffer buffer = ByteBuffer.allocateDirect(vertices.length * Float.BYTES); + // Fill buffer with the vertices. + buffer = buffer.order(ByteOrder.nativeOrder()); + buffer.asFloatBuffer().put(vertices); + buffer.rewind(); + GLVertexBuffer vbo = new GLVertexBuffer(false); + vbo.bind(); + vbo.uploadBuffer(buffer, 4, GpuUploadMethod.DATA, vertices.length * Float.BYTES); + return vbo; } @Override public void render(float delta) { System.out.println("Updated config screen with the delta of " + delta); + + GLState state = new GLState(); + init(); +// GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, GL32.GL_FRAMEBUFFER_BINDING); + GL32.glViewport(0,0, width, height); + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); +// GL32.glDisable(GL32.GL_3D); // TODO: Disable 3d for the config as we dont need it + GL32.glDisable(GL32.GL_CULL_FACE); + GL32.glDisable(GL32.GL_DEPTH_TEST); + GL32.glDisable(GL32.GL_STENCIL_TEST); + GL32.glDisable(GL32.GL_BLEND); +// GL32.glDisable(GL32.GL_SCISSOR_TEST); + + basicShader.bind(); + va.bind(); + + // Switch between the two buffers per second + if (System.currentTimeMillis() % 2000 < 1000) { + sameContextBuffer.bind(); + va.bindBufferToAllBindingPoint(sameContextBuffer.getId()); + } else { + sameContextBuffer.bind(); + va.bindBufferToAllBindingPoint(sharedContextBuffer.getId()); + } + // Render the square + GL32.glDrawArrays(GL32.GL_TRIANGLE_FAN, 0, 4); + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); + + state.restore(); } @Override diff --git a/src/main/java/com/seibel/lod/core/objects/Pos2D.java b/src/main/java/com/seibel/lod/core/objects/Pos2D.java index 13a9f224c..4e6aecdcb 100644 --- a/src/main/java/com/seibel/lod/core/objects/Pos2D.java +++ b/src/main/java/com/seibel/lod/core/objects/Pos2D.java @@ -35,6 +35,10 @@ public class Pos2D { public Pos2D subtract(Pos2D other) { return new Pos2D(x - other.x, y - other.y); } + public Pos2D subtract(int v) { + return new Pos2D(x - v, y - v); + } + public double dist(Pos2D other) { return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2)); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java index 1bcade871..13b075c8d 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java @@ -1,31 +1,70 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; -import com.seibel.lod.core.objects.a7.data.DataHandler; +import com.seibel.lod.core.objects.a7.data.DataFileHandler; +import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D; +import com.seibel.lod.core.objects.a7.render.RenderBufferHandler; +import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; public class DHLevel extends LodQuadTree { private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class); public final File saveFolder; // Could be null, for no saving - public final DataHandler dataHandler; // Could be null, for no saving - public DHLevel(File saveFolder) { + public final DataFileHandler dataFileHandler; // Could be null, for no saving + public final RenderBufferHandler renderBufferHandler; + public final ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHLevelTickerThread", 2); + private final AtomicBoolean isRunning = new AtomicBoolean(false); + public final IWorldWrapper level; + + public DHLevel(File saveFolder, IWorldWrapper level) { super(CONFIG.client().graphics().quality().getLodChunkRenderDistance()*16, MC.getPlayerBlockPos().x, MC.getPlayerBlockPos().z); this.saveFolder = saveFolder; if (saveFolder != null) { - dataHandler = new DataHandler(saveFolder); + dataFileHandler = new DataFileHandler(saveFolder); } else { - dataHandler = null; + dataFileHandler = null; } + renderBufferHandler = new RenderBufferHandler(this); + this.level = level; + } + + // Should be called by server tick thread, or called by render thread but only 20 times per second, or less? + public void update() { + + if (!isRunning.getAndSet(true)) { + dhTickerThread.submit(() -> { + try { + tick(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + isRunning.set(false); + } + }); + } + } + + private void tick() { + super.tick(new DhBlockPos2D(MC.getPlayerBlockPos())); + renderBufferHandler.update(); } @Override public RenderDataSource getRenderDataSource() { - return dataHandler; + return dataFileHandler; + } + + public void render(LodRenderProgram renderContext) { + renderBufferHandler.render(renderContext); } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java index f0c1eeea8..608728836 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java @@ -18,27 +18,30 @@ import com.seibel.lod.core.util.gridList.MovableGridRingList; * -by adding data with the lodBuilder */ public abstract class LodQuadTree { - + + /** - * TODO add static configs here - * These configs are updated someway + * //TODO add static configs here + * //These configs are updated someway + * Comment: all config value should be via the class that extends this class, and + * by implementing different abstract methods - LeeTom */ - public final int maxPossibleDetailLevel; + public final int numbersOfDetailLevels; private final MovableGridRingList[] ringLists; /** * Constructor of the quadTree - * @param viewDistance - * @param initialPlayerX - * @param initialPlayerZ + * @param viewDistance View distance in blocks + * @param initialPlayerX player x coordinate + * @param initialPlayerZ player z coordinate */ public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) { - maxPossibleDetailLevel = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2)); - ringLists = new MovableGridRingList[maxPossibleDetailLevel]; + numbersOfDetailLevels = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2)); + ringLists = new MovableGridRingList[numbersOfDetailLevels]; int size; - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { double distance = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel); int sectionCount = LodUtil.ceilDiv((int) Math.ceil(distance), DhSectionPos.getWidth(detailLevel).toBlock()) + 1; // +1 for the border during move @@ -49,19 +52,37 @@ public abstract class LodQuadTree { /** * This method return the LodSection given the Section Pos - * @param pos - * @return + * @param pos the section positon + * @return the LodSection */ public LodSection getSection(DhSectionPos pos) { return getSection(pos.detail, pos.x, pos.z); } - + + /** + * This method returns the RingList of a given detail level + * @apiNote The returned ringList should not be modified! + * @param detailLevel the detail level + * @return the RingList + */ + public MovableGridRingList getRingList(byte detailLevel) { + return ringLists[detailLevel]; + } + + /** + * This method returns the number of detail levels in the quadTree + * @return the number of detail levels + */ + public int getNumbersOfDetailLevels() { + return numbersOfDetailLevels; + } + /** * This method return the LodSection at the given detail level and level coordinate x and z - * @param detailLevel - * @param x - * @param z - * @return + * @param detailLevel detail level of the section + * @param x x coordinate of the section + * @param z z coordinate of the section + * @return the LodSection */ public LodSection getSection(byte detailLevel, int x, int z) { return ringLists[detailLevel].get(x, z); @@ -71,8 +92,8 @@ public abstract class LodQuadTree { /** * This method will compute the detail level based on player position and section pos - * @param playerPos - * @param sectionPos + * @param playerPos player position as a reference for calculating the detail level + * @param sectionPos section position * @return detail level of this section pos */ public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos) { @@ -84,7 +105,7 @@ public abstract class LodQuadTree { /** * Given a section pos at level n this method returns the parent section at level n+1 - * @param pos + * @param pos the section positon * @return the parent LodSection */ public LodSection getParentSection(DhSectionPos pos) { @@ -106,12 +127,12 @@ public abstract class LodQuadTree { /** * This function update the quadTree based on the playerPos and the current game configs (static and global) - * @param playerPos + * @param playerPos the reference position for the player */ public void tick(DhBlockPos2D playerPos) { - for (int detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (int detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { ringLists[detailLevel].move(playerPos.x >> detailLevel, playerPos.z >> detailLevel, - LodSection::immediateDispose); + LodSection::dispose); } // First tick pass: update all sections' childCount from bottom level to top level. Step: @@ -133,12 +154,12 @@ public abstract class LodQuadTree { // - set childCount to -1 (Signal that this section will be freed if not rescued) // - If targetLevel <= detail && section == null: // - Parent's childCount++ (Create parent if needed) - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { final MovableGridRingList ringList = ringLists[detailLevel]; final MovableGridRingList childRingList = detailLevel == 0 ? null : ringLists[detailLevel - 1]; final MovableGridRingList parentRingList = - detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1]; + detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; final byte detail = detailLevel; ringList.forEachPosOrdered((section, pos) -> { if (detail == 0 && section != null) { @@ -150,7 +171,7 @@ public abstract class LodQuadTree { LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); if (parent == null) { parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, - new LodSection(section.pos.getParent())); + new LodSection(section.pos.getParent(), getRenderDataSource())); parent.childCount++; } LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0); @@ -159,7 +180,7 @@ public abstract class LodQuadTree { LodSection child = ringList.get(childPos.x, childPos.z); if (child == null) { child = ringList.setChained(childPos.x, childPos.z, - new LodSection(childPos)); + new LodSection(childPos, getRenderDataSource())); child.childCount = 0; } else if (child.childCount == -1) { child.childCount = 0; @@ -181,7 +202,7 @@ public abstract class LodQuadTree { LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); if (parent == null) { parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, - new LodSection(sectPos.getParent())); + new LodSection(sectPos.getParent(), getRenderDataSource())); } parent.childCount++; } @@ -209,12 +230,12 @@ public abstract class LodQuadTree { // if childCount == -1: // (section can be loaded or unloaded, due to fast movement) // - set this section to null (TODO: Is this needed to be first or last or don't matter for concurrency?) // - If loaded unload section - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { final MovableGridRingList ringList = ringLists[detailLevel]; final MovableGridRingList childRingList = detailLevel == 0 ? null : ringLists[detailLevel - 1]; final MovableGridRingList parentRingList = - detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1]; + detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; ringList.forEachPosOrdered((section, pos) -> { LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0 || section.childCount == -1); if (section.childCount == 4) LodUtil.assertTrue( @@ -229,16 +250,12 @@ public abstract class LodQuadTree { getChildSection(section.pos, 3) == null); if (section.childCount == -1) LodUtil.assertTrue( getParentSection(section.pos).childCount == 0); - if (section.childCount == 4 && section.isLoaded()) { - section.load(getRenderDataSource()); - } else if (section.childCount == 0 && !section.isLoaded()) { section.unload(); + } else if (section.childCount == 0 && !section.isLoaded()) { + section.load(); } else if (section.childCount == -1) { ringList.set(pos.x, pos.y, null); - if (section.isLoaded()) { - section.unload(); - } section.dispose(); } }); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java index c80427f9d..ce9e80652 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java @@ -14,43 +14,34 @@ public class LodSection { // (Should always be 4 after tick() is done, or 0 only if this is an unloaded node) public byte childCount = 0; - - private RenderDataContainer levelContainer; - public RenderContainer renderContainer = null; + // TODO: Should I provide a way to change the render source? + private RenderContainer renderContainer; // Create sub region - public LodSection(DhSectionPos pos) { + public LodSection(DhSectionPos pos, RenderDataSource renderSource) { this.pos = pos; - levelContainer = null; - } - LodSection(DhSectionPos pos, RenderDataContainer levelContainer) { - this.pos = pos; - this.levelContainer = levelContainer; + this.renderContainer = renderSource.createRenderData(pos); } - // Return null if data does not exist - public boolean load(RenderDataSource renderDataSource) { - if (isLoaded()) throw new IllegalStateException("LodSection is already loaded"); - levelContainer = renderDataSource.createRenderData(pos); - return levelContainer != null; + public void load() { + LodUtil.assertTrue(!isLoaded()); + renderContainer.load(); } public void unload() { - if (!isLoaded()) throw new IllegalStateException("LodSection is not loaded"); - if (renderContainer != null) renderContainer.notifyUnload(); - levelContainer = null; - } - public void dispose() { - LodUtil.assertTrue(!isLoaded()); - if (renderContainer != null) renderContainer.notifyDispose(); + LodUtil.assertTrue(isLoaded()); + renderContainer.unload(); } - public void immediateDispose() { - if (isLoaded()) unload(); - if (renderContainer != null) renderContainer.notifyDispose(); + public void dispose() { + if (renderContainer != null) renderContainer.dispose(); } public boolean isLoaded() { - return levelContainer != null; + return renderContainer != null && renderContainer.isLoaded(); + } + + public RenderContainer getRenderContainer() { + return renderContainer; } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java index d2cb0c8e2..6aed9808b 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java @@ -1,7 +1,13 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.RenderContainer; public interface RenderDataSource { - RenderDataContainer createRenderData(DhSectionPos pos); + /** + * Returns the render container for the given section. + * @param pos The section position. + * @return The render container. If there are no data, returns EmptyRenderContainer. + */ + RenderContainer createRenderData(DhSectionPos pos); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java new file mode 100644 index 000000000..e54756299 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java @@ -0,0 +1,56 @@ +package com.seibel.lod.core.objects.a7.data; + +import com.seibel.lod.core.objects.a7.RenderDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.EmptyRenderContainer; +import com.seibel.lod.core.objects.a7.render.RenderContainer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.HashMap; + +public class DataFileHandler implements RenderDataSource { + public final File folder; + private final HashMap dataSourceCache; + + public DataFileHandler(File folderPath) { + this.folder = folderPath; + dataSourceCache = new HashMap<>(); + } + + @Override + public RenderContainer createRenderData(DhSectionPos pos) { + LodDataSource dataSource = getDataSource(pos); + RenderContainer renderContainer = RenderContainer.tryConstruct(dataSource, pos); + if (renderContainer == null) renderContainer = EmptyRenderContainer.INSTANCE; + return renderContainer; + } + + private LodDataSource getDataSource(DhSectionPos pos) { + return dataSourceCache.computeIfAbsent(pos, this::loadOrCreateDataSource); + } + + private LodDataSource loadOrCreateDataSource(DhSectionPos pos) { + File dataFile = getDataFile(pos); + if (dataFile.exists()) { + String format = getFormat(dataFile); + try { + LodDataSource data = LodDataSource.loadData(format, new FileInputStream(dataFile)); + return data; + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + return new CompleteDataContainer(); + } + + private String getFormat(File targetFile) { + return null; //TODO + } + + private File getDataFile(DhSectionPos pos) { + return null; //TODO + } + +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java deleted file mode 100644 index 897577237..000000000 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seibel.lod.core.objects.a7.data; - -import com.seibel.lod.core.objects.a7.RenderDataContainer; -import com.seibel.lod.core.objects.a7.RenderDataSource; -import com.seibel.lod.core.objects.a7.pos.DhSectionPos; - -import java.io.File; - -public class DataHandler implements RenderDataSource { - public final File folder; - - public DataHandler(File folderPath) { - this.folder = folderPath; - } - - @Override - public RenderDataContainer createRenderData(DhSectionPos pos) { - - - //TODO - return null; - } -} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java index 89ae563f0..32c9ab09f 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java @@ -1,15 +1,16 @@ package com.seibel.lod.core.objects.a7.data; +import java.io.InputStream; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.function.Function; public abstract class LodDataSource { private static final String REGISTER_STRING_FILTER_REGEX = "^[a-zA-Z0-9_]*$"; - public static final HashMap> - dataSourceLoaderRegistry = new HashMap>(); + public static final HashMap> + dataSourceLoaderRegistry = new HashMap>(); - public static void registerDataSourceLoader(String name, int version, Function loader) { + public static void registerDataSourceLoader(String name, int version, Function loader) { if (name == null || loader == null || name.isEmpty()) { throw new IllegalArgumentException("Name and loader must be non-null, and not empty"); } @@ -19,14 +20,14 @@ public abstract class LodDataSource { if (dataSourceLoaderRegistry.containsKey(name)) { throw new IllegalArgumentException("Data source loader already registered for " + name); } - dataSourceLoaderRegistry.put(name, loader); + dataSourceLoaderRegistry.put(name+"$"+version, loader); } - public static LodDataSource loadData(String dataSourceTypeName, ByteBuffer data) { + public static LodDataSource loadData(String dataSourceTypeNameVersion, InputStream data) { - Function loader = dataSourceLoaderRegistry.get(dataSourceTypeName); + Function loader = dataSourceLoaderRegistry.get(dataSourceTypeNameVersion); if (loader == null) { - throw new IllegalArgumentException("No loader for data source type " + dataSourceTypeName); + throw new IllegalArgumentException("No loader for data source type " + dataSourceTypeNameVersion); } return loader.apply(data); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/io/LevelToFileMatcher.java b/src/main/java/com/seibel/lod/core/objects/a7/io/LevelToFileMatcher.java index 9a4b43ed6..9216b76a4 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/io/LevelToFileMatcher.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/io/LevelToFileMatcher.java @@ -72,7 +72,7 @@ public class LevelToFileMatcher { if (CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity() == 0) { File saveDir = getLevelFolderWithoutSimilarityMatching(); - foundLevel = new DHLevel(saveDir); + foundLevel = new DHLevel(saveDir, currentWorld); } else { if (determiningWorldFolder.getAndSet(true)) return; //FIXME: Use a thread pool @@ -82,7 +82,7 @@ public class LevelToFileMatcher { // attempt to get the file handler File saveDir = attemptToDetermineSubDimensionFolder(); if (saveDir == null) return; - foundLevel = new DHLevel(saveDir); + foundLevel = new DHLevel(saveDir, currentWorld); } catch (IOException e) { LOGGER.error("Unable to set the dimension file handler for level [" + currentWorld + "]. Error: ", e); } finally { diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhBlockPos2D.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhBlockPos2D.java index 2161343dc..8e3ad19d7 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhBlockPos2D.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhBlockPos2D.java @@ -1,5 +1,6 @@ package com.seibel.lod.core.objects.a7.pos; +import com.seibel.lod.core.objects.DHBlockPos; import com.seibel.lod.core.objects.Pos2D; import com.seibel.lod.core.util.LodUtil; @@ -11,6 +12,11 @@ public class DhBlockPos2D { this.z = z; } + public DhBlockPos2D(DHBlockPos blockPos) { + this.x = blockPos.x; + this.z = blockPos.z; + } + public DhBlockPos2D add(DhBlockPos2D other) { return new DhBlockPos2D(x + other.x, z + other.z); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java index fd8d4afad..14f484961 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java @@ -1,24 +1,46 @@ package com.seibel.lod.core.objects.a7.render; +import com.seibel.lod.core.objects.a7.LodSection; +import com.seibel.lod.core.objects.a7.RenderDataContainer; +import com.seibel.lod.core.objects.a7.data.LodDataSource; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.opengl.RenderBuffer; import com.seibel.lod.core.objects.opengl.RenderRegion; import com.seibel.lod.core.util.LodUtil; +import java.util.concurrent.atomic.AtomicReference; + public class ColumnRenderContainer extends RenderContainer { public static final int columnWidth = DhSectionPos.DATA_WIDTH_PER_SECTION; public static final int columnCount = LodUtil.pow2(DhSectionPos.DATA_WIDTH_PER_SECTION); - private long[] columnData; + + public RenderDataContainer dataContainer = null; + public final int maxColumnHeight; public final int minWorldHeight; - public RenderRegion renderRegion = null; + public static RenderContainer testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos) { + ColumnRenderContainer container = new ColumnRenderContainer(10, -100); //FIXME: Use actual config value + container.startFillData(dataSource); + return container; + } + static { + RenderContainer.registorLoader(ColumnRenderContainer::testAndConstruct, 0); + } public ColumnRenderContainer(int maxColumnHeight, int minWorldHeight) { this.maxColumnHeight = maxColumnHeight; - columnData = new long[columnCount * maxColumnHeight]; + //columnData = new long[columnCount * maxColumnHeight]; this.minWorldHeight = minWorldHeight; - renderRegion = new RenderRegion(); + } + + private void startFillData(LodDataSource dataSource) { + //TODO + } + + @Override + public void notifyLoad() { + } @Override @@ -32,8 +54,7 @@ public class ColumnRenderContainer extends RenderContainer { } @Override - public RenderRegion getRenderRegion() { - return null; + public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { + return false; } - } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java index 95afea72c..cf63d8d5c 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java @@ -1,6 +1,17 @@ package com.seibel.lod.core.objects.a7.render; +import com.seibel.lod.core.objects.opengl.RenderBuffer; + +import java.util.concurrent.atomic.AtomicReference; + public class EmptyRenderContainer extends RenderContainer { + public static final EmptyRenderContainer INSTANCE = new EmptyRenderContainer(); + + @Override + public void notifyLoad() { + + } + @Override public void notifyUnload() { @@ -12,7 +23,7 @@ public class EmptyRenderContainer extends RenderContainer { } @Override - public boolean render() { - return true; //Always render successfully since there is nothing to render + public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { + return false; // no swap } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java new file mode 100644 index 000000000..cc12d1111 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java @@ -0,0 +1,144 @@ +package com.seibel.lod.core.objects.a7.render; + +import com.seibel.lod.core.objects.Pos2D; +import com.seibel.lod.core.objects.a7.LodQuadTree; +import com.seibel.lod.core.objects.a7.LodSection; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.opengl.RenderBuffer; +import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.gridList.MovableGridRingList; + +import java.util.concurrent.atomic.AtomicReference; + +public class RenderBufferHandler { + public final LodQuadTree target; + private final MovableGridRingList renderBufferNodes; + + class RenderBufferNode implements AutoCloseable { + public final DhSectionPos pos; + public volatile RenderBufferNode[] children = null; + public AtomicReference renderBufferSlot = null; + + public RenderBufferNode(DhSectionPos pos) { + this.pos = pos; + } + + public void render(LodRenderProgram renderContext) { + RenderBuffer buff = renderBufferSlot.get(); + if (buff != null) { + buff.render(renderContext); + } else { + RenderBufferNode[] childs = children; + if (childs != null) { + for (RenderBufferNode child : childs) { + child.render(renderContext); + } + } + } + } + + //TODO: In the future make this logic a bit more complex so that when children are just created, + // the buffer is only unloaded if all children's buffers are ready. This will make the + // transition between buffers no longer causing any flicker. + public void update() { + LodSection section = target.getSection(pos); + // If this fails, there may be concurrent modification of the quad tree + // (as this update() should be called from the same thread that calls update() on the quad tree) + LodUtil.assertTrue(section != null); + RenderContainer container = section.getRenderContainer(); + + // Update self's render buffer state + boolean shouldRender = section.isLoaded(); + if (!shouldRender) { + RenderBuffer buff = renderBufferSlot.getAndSet(null); + if (buff != null) { + buff.close(); + } + } else { + container.trySwapRenderBuffer(renderBufferSlot); + } + + // Update children's render buffer state + boolean shouldHaveChildren = !container.isLoaded(); + if (shouldHaveChildren) { + if (children == null) { + RenderBufferNode[] childs = new RenderBufferNode[4]; + for (int i = 0; i < 4; i++) { + childs[i] = new RenderBufferNode(pos.getChild(i)); + } + children = childs; + } + for (RenderBufferNode child : children) { + child.update(); + } + } else { + if (children != null) { + RenderBufferNode[] childs = children; + children = null; + for (RenderBufferNode child : childs) { + child.close(); + } + } + } + } + + @Override + public void close() { + if (children != null) { + for (RenderBufferNode child : children) { + child.close(); + } + } + RenderBuffer buff = renderBufferSlot.getAndSet(null); + if (buff != null) { + buff.close(); + } + } + } + + public RenderBufferHandler(LodQuadTree target) { + this.target = target; + MovableGridRingList referenceList = target.getRingList((byte) (target.getNumbersOfDetailLevels() - 1)); + Pos2D center = referenceList.getCenter(); + renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center); + } + + public void render(LodRenderProgram renderContext) { + //TODO: This might get locked by update() causing move() call. Is there a way to avoid this? + // Maybe dupe the base list and use atomic swap on render? Or is this not worth it? + renderBufferNodes.forEachOrdered(n -> n.render(renderContext)); + } + + public void update() { + byte topDetail = (byte) (target.getNumbersOfDetailLevels() - 1); + MovableGridRingList referenceList = target.getRingList(topDetail); + Pos2D center = referenceList.getCenter(); + renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list + renderBufferNodes.forEachPosOrdered((node, pos) -> { + DhSectionPos sectPos = new DhSectionPos(topDetail, pos.x, pos.y); + LodSection section = target.getSection(sectPos); + + if (section == null) { + // If section is null, but node exists, remove node + if (node != null) { + renderBufferNodes.remove(pos).close(); + } + // If section is null, continue + return; + } + + // If section is not null, but node does not exist, create node + if (node == null) { + node = renderBufferNodes.setChained(pos, new RenderBufferNode(sectPos)); + } + // Node should be not null here + // Update node + node.update(); + }); + } + + public void close() { + renderBufferNodes.clear(RenderBufferNode::close); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java index 1321cbc7f..9062b75ce 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java @@ -1,13 +1,66 @@ package com.seibel.lod.core.objects.a7.render; +import com.seibel.lod.core.objects.a7.LodSection; +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.opengl.RenderBuffer; -import com.seibel.lod.core.objects.opengl.RenderRegion; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; public abstract class RenderContainer { + interface RenderContainerConstructor { + // Can return null as meaning the requirement is not met + RenderContainer testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos); + } + public static final SortedMap + renderContainerLoaderRegistry = new TreeMap(); + public static void registorLoader(RenderContainerConstructor func, int priority) { + if (func == null) { + throw new IllegalArgumentException("loader must be non-null"); + } + renderContainerLoaderRegistry.put(priority, func); + } - public abstract void notifyUnload(); - public abstract void notifyDispose(); + public static RenderContainer tryConstruct(LodDataSource dataSource, DhSectionPos pos) { + for (RenderContainerConstructor func : renderContainerLoaderRegistry.values()) { + RenderContainer container = func.testAndConstruct(dataSource, pos); + if (container != null) { + return container; + } + } + return null; + } - public abstract RenderRegion getRenderRegion(); + private boolean isLoaded = false; + public final void load() { + isLoaded = true; + notifyLoad(); + } + public final void unload() { + isLoaded = false; + notifyUnload(); + } + public final void dispose() { + if (isLoaded) { + unload(); + } + notifyDispose(); + } + public final boolean isLoaded() { + return isLoaded; + } + + protected abstract void notifyLoad(); // notify the container that it is now loaded and therefore may be rendered + protected abstract void notifyUnload(); // notify the container that it is now unloaded and therefore will not be rendered + protected abstract void notifyDispose(); // notify the container that the parent lodSection is now disposed + + /** + * Try and swap in new render buffer for this section. Note that before this call, there should be no other + * places storing or referencing the render buffer. + * @param referenceSlot The slot for swapping in the new buffer. + * @return True if the swap was successful. False if swap is not needed or if it is in progress. + */ + public abstract boolean trySwapRenderBuffer(AtomicReference referenceSlot); } diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java index 463ff8caa..0d7c0c1ce 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java @@ -63,7 +63,8 @@ public class RenderRegion implements AutoCloseable /** stores if the region at the given x and z index needs to be regenerated */ // Use int because I need Tri state: private final AtomicInteger needRegen = new AtomicInteger(2); - + + private enum BackState { Unused, Building, diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index 4bfbae080..ba7fd7e64 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -21,6 +21,8 @@ package com.seibel.lod.core.util; import java.io.File; import java.util.Iterator; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import com.seibel.lod.core.enums.config.ServerFolderNameMode; import com.seibel.lod.core.enums.config.VanillaOverdraw; @@ -446,5 +448,16 @@ public class LodUtil public static void assertTrue(boolean condition) { if (!condition) throw new RuntimeException("Assertion failed"); } - + public static ExecutorService makeSingleThreadPool(String name, int relativePriority) { + return Executors.newSingleThreadExecutor(new LodThreadFactory(name, Thread.NORM_PRIORITY+relativePriority)); + } + public static ExecutorService makeSingleThreadPool(Class clazz, int relativePriority) { + return makeSingleThreadPool(clazz.getSimpleName(), relativePriority); + } + public static ExecutorService makeSingleThreadPool(String name) { + return makeSingleThreadPool(name, 0); + } + public static ExecutorService makeSingleThreadPool(Class clazz) { + return makeSingleThreadPool(clazz.getSimpleName(), 0); + } } diff --git a/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java b/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java index 0fc16ef69..643d38e91 100644 --- a/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java +++ b/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java @@ -41,6 +41,7 @@ public class MovableGridRingList extends ArrayList implements List { private Pos2D[] ringIteratorList = null; //TODO: Check if this needs to be synchronized + //FIXME: Make all usage of this class do stuff relative to the minPos instead of the center private void buildRingIteratorList() { ringIteratorList = null; Pos2D[] list = new Pos2D[size*size]; @@ -67,6 +68,9 @@ public class MovableGridRingList extends ArrayList implements List { pos.set(new Pos2D(centerX-halfSize, centerY-halfSize)); clear(); } + public MovableGridRingList(int halfSize, Pos2D center) { + this(halfSize, center.x, center.y); + } @Override public void clear() { @@ -173,6 +177,23 @@ public class MovableGridRingList extends ArrayList implements List { } } + public T remove(int x, int y) { + return swap(x, y, null); + } + + public T get(Pos2D p) { + return get(p.x, p.y); + } + public boolean set(Pos2D p, T t) { + return set(p.x, p.y, t); + } + public T swap(Pos2D p, T t) { + return swap(p.x, p.y, t); + } + public T remove(Pos2D p) { + return remove(p.x, p.y); + } + // TODO: Impl this /* // do a compare and set @@ -196,6 +217,9 @@ public class MovableGridRingList extends ArrayList implements List { public T setChained(int x, int y, T t) { return set(x,y,t) ? t : null; } + public T setChained(Pos2D p, T t) { + return setChained(p.x, p.y, t); + } // Return false if haven't changed. Return true if it did public boolean move(int newCenterX, int newCenterY) {