Merge remote-tracking branch 'origin/main' into main

This commit is contained in:
Morippi
2022-05-11 14:54:22 +02:00
18 changed files with 561 additions and 115 deletions
@@ -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
@@ -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));
}
@@ -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);
}
}
@@ -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<LodSection>[] 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<LodSection> 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<LodSection> ringList = ringLists[detailLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
final MovableGridRingList<LodSection> 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<LodSection> ringList = ringLists[detailLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
final MovableGridRingList<LodSection> 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();
}
});
@@ -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;
}
}
@@ -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);
}
@@ -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<DhSectionPos, LodDataSource> 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
}
}
@@ -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;
}
}
@@ -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<String, Function<ByteBuffer,? extends LodDataSource>>
dataSourceLoaderRegistry = new HashMap<String, Function<ByteBuffer,? extends LodDataSource>>();
public static final HashMap<String, Function<InputStream,? extends LodDataSource>>
dataSourceLoaderRegistry = new HashMap<String, Function<InputStream,? extends LodDataSource>>();
public static void registerDataSourceLoader(String name, int version, Function<ByteBuffer,? extends LodDataSource> loader) {
public static void registerDataSourceLoader(String name, int version, Function<InputStream,? extends LodDataSource> 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<ByteBuffer,? extends LodDataSource> loader = dataSourceLoaderRegistry.get(dataSourceTypeName);
Function<InputStream,? extends LodDataSource> 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);
}
@@ -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 {
@@ -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);
}
@@ -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<RenderBuffer> referenceSlot) {
return false;
}
}
@@ -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<RenderBuffer> referenceSlot) {
return false; // no swap
}
}
@@ -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<RenderBufferNode> renderBufferNodes;
class RenderBufferNode implements AutoCloseable {
public final DhSectionPos pos;
public volatile RenderBufferNode[] children = null;
public AtomicReference<RenderBuffer> 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<LodSection> 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<LodSection> 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);
}
}
@@ -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<Integer, RenderContainerConstructor>
renderContainerLoaderRegistry = new TreeMap<Integer, RenderContainerConstructor>();
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<RenderBuffer> referenceSlot);
}
@@ -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,
@@ -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);
}
}
@@ -41,6 +41,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
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<T> extends ArrayList<T> implements List<T> {
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<T> extends ArrayList<T> implements List<T> {
}
}
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<T> extends ArrayList<T> implements List<T> {
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) {