Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user