I tried to do threading to load chunk of voxels It worked in series but when implemented in parallel threading it was not generating the chunks, It worked when I did this in series, I even used Chatgpt and other AI tools but It didn’t help as expected Here is my code (please don’t judge It’s been a little edited by AI XD)
import threading
import queue
from settings import *
from world_objects.chunk import Chunk
from voxel_handler import VoxelHandler
class World:
def __init__(self, app):
self.app = app
self.world_height = WORLD_H + BUILD_C_HEIGHT_LIMIT
self.chunks = {}
self.voxel_pointers = np.empty([WORLD_W * WORLD_D * self.world_height, CHUNK_VOLUME], dtype='uint8')
self.voxel_handler = VoxelHandler(self)
self.render_distance = 7
self.chunk_queue = queue.Queue()
self.main_thread_queue = queue.Queue()
self.stop_threads = False
self.update_active_chunks()
self.chunk_thread = threading.Thread(target=self.chunk_worker, daemon=True)
self.chunk_thread.start()
def update(self):
self.voxel_handler.update()
self.update_active_chunks()
self.process_main_thread_tasks()
def chunk_worker(self):
while not self.stop_threads:
try:
pos = self.chunk_queue.get(timeout=1)
print(f"Generating chunk at {pos}") # Debug print
self.generate_chunk_data(pos)
self.chunk_queue.task_done()
except queue.Empty:
continue
def process_main_thread_tasks(self):
while not self.main_thread_queue.empty():
task = self.main_thread_queue.get()
task()
self.main_thread_queue.task_done()
def update_active_chunks(self):
player_pos = self.app.player.position
print(f"Player position: {player_pos}")
player_chunk_pos = (
int(player_pos[0] // CHUNK_SIZE),
int(player_pos[1] // CHUNK_SIZE),
int(player_pos[2] // CHUNK_SIZE)
)
render_x_min = max(0, player_chunk_pos[0] - self.render_distance)
render_x_max = min(WORLD_W, player_chunk_pos[0] + self.render_distance)
render_y_min = max(0, player_chunk_pos[1] - self.render_distance)
render_y_max = min(self.world_height, player_chunk_pos[1] + self.render_distance)
render_z_min = max(0, player_chunk_pos[2] - self.render_distance)
render_z_max = min(WORLD_D, player_chunk_pos[2] + self.render_distance)
active_chunk_positions = {
(x, y, z)
for x in range(render_x_min, render_x_max)
for y in range(render_y_min, render_y_max)
for z in range(render_z_min, render_z_max)
}
print(f"Active chunk positions: {active_chunk_positions}")
self.load_and_unload_chunks(active_chunk_positions)
def load_and_unload_chunks(self, active_chunk_position):
current_chunk_positions = set(self.chunks.keys())
chunks_to_load = active_chunk_position - current_chunk_positions
chunks_to_unload = current_chunk_positions - active_chunk_position
for pos in chunks_to_load:
print(f"Queueing chunk for loading at {pos}")
self.chunk_queue.put(pos)
for pos in chunks_to_unload:
self.unload_chunk(pos)
def generate_chunk_data(self, pos):
# Generate chunk data without OpenGL operations
x, y, z = pos
chunk = Chunk(self, position=pos)
chunk_index = x + WORLD_W * z + WORLD_AREA * y
self.voxel_pointers[chunk_index] = chunk.build_voxels()
chunk.voxels = self.voxel_pointers[chunk_index]
# Add chunk to chunks dictionary (thread-safe)
with threading.Lock():
self.chunks[pos] = chunk
# Queue the chunk for mesh building in the main thread
self.app.queue_main_thread_task(lambda: self.build_chunk_mesh(chunk))
def build_chunk_mesh(self, chunk):
try:
chunk.build_mesh()
print(f"Chunk mesh built at {chunk.position}")
except Exception as e:
print(f"Error building mesh for chunk at {chunk.position}: {e}")
self.unload_chunk(chunk.position)
def unload_chunk(self, pos):
if pos in self.chunks:
print(f"Unloading chunk at {pos}")
del self.chunks[pos]
def render(self):
for chunk in self.chunks.values():
if chunk and chunk.mesh and chunk.mesh.program:
print(f"Rendering chunk at {chunk.position}") # Debug print
chunk.render()
else:
if chunk:
print(f"Chunk at {chunk.position} not rendered due to uninitialized mesh or program.")
else:
print(f"Chunk is None")
def queue_main_thread_task(self, task):
self.main_thread_queue.put(task)
def shutdown(self):
self.stop_threads = True
self.chunk_thread.join()
I use moderngl, pygame, numpy, numba There are no voxels or chunks creating in this can someone please help I can’t sleep at night thinking about what I did wrong
Edited:
The series code when it worked:
from settings import *
from world_objects.chunk import Chunk
from voxel_handler import VoxelHandler
import numpy as np
class World:
def __init__(self, app):
self.app = app
self.world_height = WORLD_H + BUILD_C_HEIGHT_LIMIT
# Dictionary to store chunks with keys as tuple positions (x, y, z)
self.chunks = {}
# 3D numpy array to hold pointers to voxels in each chunk
self.voxel_pointers = np.empty([WORLD_W * WORLD_D * self.world_height, CHUNK_VOLUME], dtype='uint8')
self.voxel_handler = VoxelHandler(self)
# Set render distance in chunks (define this according to your needs)
self.render_distance = 4
# Set initial chunks based on player's starting position
self.update_active_chunks()
def update(self):
self.voxel_handler.update()
self.update_active_chunks()
def update_active_chunks(self):
"""
Update the set of active chunks based on the player's current position.
"""
player_pos = self.app.player.position
player_chunk_pos = (
int(player_pos[0] // CHUNK_SIZE),
int(player_pos[1] // CHUNK_HEIGHT),
int(player_pos[2] // CHUNK_SIZE)
)
# Calculate the bounds of the render area
render_x_min = max(0, player_chunk_pos[0] - self.render_distance)
render_x_max = min(WORLD_W, player_chunk_pos[0] + self.render_distance)
render_y_min = max(0, player_chunk_pos[1] - self.render_distance)
render_y_max = min(self.world_height, player_chunk_pos[1] + self.render_distance)
render_z_min = max(0, player_chunk_pos[2] - self.render_distance)
render_z_max = min(WORLD_D, player_chunk_pos[2] + self.render_distance)
# Create a set of positions for the active chunks
active_chunk_positions = {
(x, y, z)
for x in range(render_x_min, render_x_max)
for y in range(render_y_min, render_y_max)
for z in range(render_z_min, render_z_max)
}
# Load new chunks and unload chunks that are out of the render distance
self.load_and_unload_chunks(active_chunk_positions)
def load_and_unload_chunks(self, active_chunk_positions):
"""
Load new chunks and unload those that are no longer within the active set.
"""
current_chunk_positions = set(self.chunks.keys())
# Chunks to be loaded
chunks_to_load = active_chunk_positions - current_chunk_positions
# Chunks to be unloaded
chunks_to_unload = current_chunk_positions - active_chunk_positions
# Load new chunks
for pos in chunks_to_load:
x, y, z = pos
chunk = Chunk(self, position=pos)
chunk_index = x + WORLD_W * z + WORLD_AREA * y
# Initialize voxel pointers for the chunk
self.voxel_pointers[chunk_index] = chunk.build_voxels()
# Assign voxels to chunk
chunk.voxels = self.voxel_pointers[chunk_index]
# Store the chunk
self.chunks[pos] = chunk
# Build the chunk mesh
chunk.build_mesh()
# Unload old chunks
for pos in chunks_to_unload:
self.unload_chunk(pos)
def unload_chunk(self, pos):
"""
Unload the chunk at the given position.
"""
if pos in self.chunks:
del self.chunks[pos]
def render(self):
for chunk in self.chunks.values():
chunk.render()
The only change I did was implement a threading and queue in the code, I used deamon and shutdown() because it was still running after I closed the window which displays chunks, according to the debug messages the active chunks are loaded correctly but the build function where chunk.build_mesh() is not running
The chunk.build_mesh() is in another file
def build_mesh(self):
if not self.is_empty:
self.mesh = ChunkMesh(self)
The ChunkMesh function builds shaders and vertex position to form a triangles
If you want to see the total thread of file here
ChunkMesh
from Meshes.base_mesh import BaseMesh
from Meshes.chunk_mesh_builder import build_chunk_mesh
class ChunkMesh(BaseMesh):
def __init__(self, chunk):
self.app = chunk.app
self.chunk = chunk
self.ctx = self.app.ctx
self.program = self.app.shader_program.chunk
self.vbo_format = '1u4'
self.format_size = sum(int(fmt[:1]) for fmt in self.vbo_format.split())
self.attrs = ('packed_data',)
self.vao = self.get_vao()
def rebuild(self):
self.vao = self.get_vao()
def get_vertex_data(self):
mesh = build_chunk_mesh(
chunk_voxel=self.chunk.voxels,
format_size=self.format_size,
chunk_pos=self.chunk.position,
world_voxels=self.chunk.world.voxel_pointers,
)
return mesh
BaseMesh
import numpy as np
class BaseMesh:
def __init__(self):
self.ctx = None
self.program = None
# vertex buffer data type format
self.vbo_format = None
# attribute names according to the format
self.attrs: tuple[str, ...] = None
# vertex array object
self.vao = None
def get_vertex_data(self) -> np.array: ...
def get_vao(self):
vertex_data = self.get_vertex_data()
# It holds the information of the shape
vbo = self.ctx.buffer(vertex_data)
# It is sort of container for VBOs
vao = self.ctx.vertex_array(
self.program, [(vbo, self.vbo_format, *self.attrs)], skip_errors=True
)
# Note: it is good to have less VAO data than large VBOs
return vao
def render(self):
self.vao.render()
The debug messages:
When program is running:-
Generating chunk at position: (5, 1, 6)
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
Chunk at (5, 1, 6) not rendered due to uninitialized mesh or program.
When I force stopped the program:-
Error building mesh for chunk at (5, 1, 6): cannot create buffer
Unloading chunk at (5, 1, 6)
Note: build_chunk_mesh is just co-ordinates to place and connect the voxel vertex
11