moved chunkgen to 2nd thread, initial solution

This commit is contained in:
Antti Hakkarainen 2023-02-13 21:24:55 +02:00
parent 675b451723
commit ad598f980e
7 changed files with 134 additions and 70 deletions

View file

@ -3,12 +3,13 @@ extends TileMap
var x:int = -1 var x:int = -1
var y:int = -1 var y:int = -1
var should_remove:bool = true var should_remove:bool = false
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _init(xpos:int, ypos:int): func _init(ypos:int, xpos:int, sr: bool):
self.x = xpos self.x = xpos
self.y = ypos self.y = ypos
self.should_remove = sr
self.name = "Chunk [%d,%d]" % [x, y] self.name = "Chunk [%d,%d]" % [x, y]
self.set_tileset(Globals.TILESET_TERRAIN) self.set_tileset(Globals.TILESET_TERRAIN)

View file

@ -5,17 +5,22 @@ extends Camera2D
var is_panning_camera = false var is_panning_camera = false
var tween var tween
func camera_zoom_in() -> void: func camera_zoom_in() -> void:
_set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL - Globals.CAMERA_ZOOM_FACTOR) _set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL - Globals.CAMERA_ZOOM_FACTOR)
func camera_zoom_out() -> void: func camera_zoom_out() -> void:
_set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL + Globals.CAMERA_ZOOM_DURATION) _set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL + Globals.CAMERA_ZOOM_DURATION)
func get_camera_position(): func get_camera_position():
return self.position return self.position
func _process(delta) -> void:
func _process(_delta) -> void:
Globals.CAMERA_POSITION = self.position Globals.CAMERA_POSITION = self.position
func _set_camera_zoom_level(value: float) -> void: func _set_camera_zoom_level(value: float) -> void:
Globals.CAMERA_ZOOM_LEVEL = clamp(value, Globals.CAMERA_MIN_ZOOM_LEVEL, Globals.CAMERA_MAX_ZOOM_LEVEL) Globals.CAMERA_ZOOM_LEVEL = clamp(value, Globals.CAMERA_MIN_ZOOM_LEVEL, Globals.CAMERA_MAX_ZOOM_LEVEL)
@ -29,9 +34,11 @@ func _set_camera_zoom_level(value: float) -> void:
Globals.CAMERA_ZOOM_DURATION Globals.CAMERA_ZOOM_DURATION
) )
func _on_set_camera_position(pos: Vector2) -> void: func _on_set_camera_position(pos: Vector2) -> void:
self.position = pos self.position = pos
func _unhandled_input(event): func _unhandled_input(event):
if event.is_action_pressed("camera_zoom_in"): if event.is_action_pressed("camera_zoom_in"):
camera_zoom_in() camera_zoom_in()

View file

@ -10,9 +10,12 @@ extends Node2D
# which is extremely slow in godot 4.0, 4096x4096 takes minutes to fill with set_cell() commands # which is extremely slow in godot 4.0, 4096x4096 takes minutes to fill with set_cell() commands
var chunks:Dictionary = {} var chunks:Dictionary = {}
var unready_chunks:Dictionary = {}
var window_width = DisplayServer.window_get_size(0).x var window_width = DisplayServer.window_get_size(0).x
var distance = abs((window_width/(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X)) / 2 +1 ) var distance = abs((window_width/(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X)) / 2 +1 )
var mutex:Mutex
var semaphore:Semaphore
var thread:Thread
var exit_thread = false
func _init() -> void: func _init() -> void:
@ -20,44 +23,73 @@ func _init() -> void:
func _process(_delta): func _process(_delta):
# if !Globals.worlgen_ready || Globals.chunk_queue.size() > 64:
# return
update_chunks() update_chunks()
clean_up_chunks() clean_up_chunks()
reset_chunks() reset_chunks()
#print(self.get_child_count())
func _ready(): func _ready():
#thread = Thread.new() mutex = Mutex.new()
#print(distance) semaphore = Semaphore.new()
pass exit_thread = false
thread = Thread.new()
thread.start(start_chunkgen, Thread.PRIORITY_NORMAL)
func start_chunkgen():
while true:
semaphore.wait()
mutex.lock()
var should_exit = exit_thread # Protect with Mutex.
mutex.unlock()
if should_exit:
break
if Globals.chunk_queue.size() > 0:
mutex.lock()
var vars = Globals.chunk_queue.pop_front()
mutex.unlock()
load_chunk(vars[0].y, vars[0].x, vars[1])
# Thread must be disposed (or "joined"), for portability.
func _exit_tree():
# Set exit condition to true.
mutex.lock()
exit_thread = true # Protect with Mutex.
mutex.unlock()
# Unblock by posting.
semaphore.post()
# Wait until it exits.
thread.wait_to_finish()
func add_chunk(x:int, y:int) -> void:
var key = str(y) + "," + str(x)
if chunks.has(key):
return
load_chunk(x, y, key)
func clean_up_chunks(): func clean_up_chunks():
for key in chunks: for key in chunks:
var chunk = chunks[key] var chunk = chunks[key]
if chunk.should_remove: if chunk.should_remove:
chunk.queue_free() chunk.queue_free()
chunks.erase(key) chunks.erase(key)
func clear_chunk(pos:Vector2i) -> void:
self.chunks[pos.y][pos.x].clear()
func correction_factor(distance) -> float: func correction_factor(d) -> float:
if Globals.CAMERA_ZOOM_LEVEL < 0.6: if Globals.CAMERA_ZOOM_LEVEL < 0.6:
return distance * 2.0 return d * 2.0
elif Globals.CAMERA_ZOOM_LEVEL > 1.0: elif Globals.CAMERA_ZOOM_LEVEL > 1.0:
return distance return d
else: else:
return distance * ( 1 + 2 * (1-Globals.CAMERA_ZOOM_LEVEL) ) return d * ( 1 + 2 * (1-Globals.CAMERA_ZOOM_LEVEL) )
func get_chunk(x:int, y:int): func get_chunk(x:int, y:int):
var key = str(y) + "," + str(x) var key = str(y) + "," + str(x)
@ -65,22 +97,26 @@ func get_chunk(x:int, y:int):
return chunks.get(key) return chunks.get(key)
return null return null
func load_chunk(x:int, y:int, key:String):
var chunk = Chunk.new(x,y) func load_chunk(y:int, x:int, key):
self.add_child(chunk) var chunk = Chunk.new(y, x, false)
call_deferred("add_child", chunk)
mutex.lock()
chunks[key] = chunk chunks[key] = chunk
mutex.unlock()
Globals.chunks_loaded += 1
func reset_chunks(): func reset_chunks():
# avoid trying to edit already removed chunks
mutex.lock()
for key in chunks: for key in chunks:
chunks[key].should_remove = true chunks[key].should_remove = true
mutex.unlock()
func update_chunks(): func update_chunks():
var p_x = int(Globals.CAMERA_POSITION.x- Globals.CHUNK_SIZE.x) / Globals.TILE_SIZE_X / Globals.CHUNK_SIZE.x var p_x = int(Globals.CAMERA_POSITION.x- Globals.CHUNK_SIZE.x) / Globals.TILE_SIZE_X / Globals.CHUNK_SIZE.x
var p_y = int(Globals.CAMERA_POSITION.y- Globals.CHUNK_SIZE.y) / Globals.TILE_SIZE_Y / Globals.CHUNK_SIZE.y var p_y = int(Globals.CAMERA_POSITION.y- Globals.CHUNK_SIZE.y) / Globals.TILE_SIZE_Y / Globals.CHUNK_SIZE.y
@ -88,13 +124,23 @@ func update_chunks():
# based on current zoom level. # based on current zoom level.
var zoom_corrected = correction_factor(distance) var zoom_corrected = correction_factor(distance)
# iterate through all the chunks. if a chunk is in camera range,
# and it exists, it should not be removed
# if chunk should be in range and it doesn't exist, it will be
# created by adding the coords to a work queue
for y in Globals.map_size/Globals.CHUNK_SIZE.y: for y in Globals.map_size/Globals.CHUNK_SIZE.y:
for x in Globals.map_size/Globals.CHUNK_SIZE.x: for x in Globals.map_size/Globals.CHUNK_SIZE.x:
if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected): if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected):
add_chunk(x, y) var key = str(y) + "," + str(x)
if chunks.has(key):
var chunk = get_chunk(x,y) var chunk = get_chunk(x,y)
if chunk != null: if chunk != null:
chunk.should_remove = false chunk.should_remove = false
else:
mutex.lock()
Globals.chunk_queue.push_back([Vector2i(x, y), key])
mutex.unlock()
semaphore.post()

View file

@ -26,7 +26,7 @@ func _process(_delta):
str(get_viewport().get_mouse_position()) +"\n" + str(get_viewport().get_mouse_position()) +"\n" +
"FPS " + str(Engine.get_frames_per_second()) + "\n" + "FPS " + str(Engine.get_frames_per_second()) + "\n" +
"Zoom lvl: " + str(Globals.CAMERA_ZOOM_LEVEL) + "\n" + "Zoom lvl: " + str(Globals.CAMERA_ZOOM_LEVEL) + "\n" +
"Chunks loaded: " + str(Globals.chunks_loaded) + "\n" + "Chunk queue: " + str(Globals.chunk_queue.size()) + "\n" +
"Camera pos: " + str(Globals.CAMERA_POSITION) "Camera pos: " + str(Globals.CAMERA_POSITION)
) )

View file

@ -4,23 +4,25 @@
extends Node extends Node
var chunks_loaded:int = 0 var chunks_loaded:int = 0
var chunk_queue:Array = []
var worlgen_ready:bool = false
################################### ###################################
# CHUNK AND TERRAIN SETTINGS # # CHUNK AND TERRAIN SETTINGS #
################################### ###################################
# world map chunk size # world map chunk size
const CHUNK_SIZE:Vector2i = Vector2i(32,32) const CHUNK_SIZE:Vector2i = Vector2i(32,32)
# tilemap tile types # tilemap tile types
enum {TILE_WATER, TILE_TERRAIN, TILE_FOREST, TILE_BOG} enum {TILE_WATER, TILE_TERRAIN, TILE_FOREST, TILE_BOG}
# tilemap layers # tilemap layers
enum {LAYER_TERRAIN, LAYER_BUILDINGS} enum {LAYER_TERRAIN, LAYER_BUILDINGS}
const TILESET_TERRAIN:TileSet = preload("res://scenes/Chunk.tres") const TILESET_TERRAIN:TileSet = preload("res://scenes/Chunk.tres")
func choose_randomly(list_of_entries):
return list_of_entries[randi() % list_of_entries.size()]
# map size is based on input image x*y pixel size # map size is based on input image x*y pixel size
var map_size:int var map_size:int = 0
# store terrain type (water, land, forest etc. for every map cell) # store terrain type (water, land, forest etc. for every map cell)
var map_terrain_data:Array[Array] = [[]] var map_terrain_data:Array[Array] = [[]]

View file

@ -31,29 +31,22 @@ func _init():
# Vector2i(3800,2000) # Vector2i(3800,2000)
# ) # )
Globals.CAMERA_POSITION = Vector2(16*256/2, 16*256/2) Globals.CAMERA_POSITION = Vector2(16*256/2, 16*256/2)
#func _process(_delta):
# Globals.CAMERA_POSITION = _2d_camera.position
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready(): func _ready():
# create a new world and worldgenerator # create a new world and worldgenerator
_world_generator = WorldGenerator.new() _world_generator = WorldGenerator.new()
_chunk_handler = ChunkHandler.new()
#_2d_camera = CameraZoom2D.new() #_2d_camera = CameraZoom2D.new()
# add chunk handler if worldgen was successful # add chunk handler if worldgen was successful
if _world_generator.generate_world(map_filename): if _world_generator.generate_world(map_filename):
Globals.worlgen_ready = true
_chunk_handler = ChunkHandler.new()
add_child(_chunk_handler) add_child(_chunk_handler)
#add_child(_2d_camera)
# for y in Globals.map_size/Globals.CHUNK_SIZE.y:
# for x in Globals.map_size/Globals.CHUNK_SIZE.x:
# #if (y + x) % 2 == 0:
# _chunk_handler.load_chunk(x, y)
else: else:
push_error("World generation failed :-(") push_error("World generation failed :-(")
# center camera to world map # center camera to world map
emit_signal( emit_signal(
"set_camera_position", "set_camera_position",

View file

@ -3,6 +3,7 @@ extends RefCounted
var image:Image = Image.new() var image:Image = Image.new()
var directions:Array = [ var directions:Array = [
Vector2i(0,1), # south Vector2i(0,1), # south
Vector2i(1,0), # east Vector2i(1,0), # east
@ -10,6 +11,7 @@ var directions:Array = [
Vector2i(-1,0) # west Vector2i(-1,0) # west
] ]
func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool: func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool:
if bounds.x > value or value > bounds.y: if bounds.x > value or value > bounds.y:
errmsg = errmsg % [value, bounds.x, bounds.y] errmsg = errmsg % [value, bounds.x, bounds.y]
@ -17,6 +19,11 @@ func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool:
return false return false
return true return true
func choose_randomly(list_of_entries):
return list_of_entries[randi() % list_of_entries.size()]
func choose_tile(tile:Vector2i, selected, surrounding) -> Array: func choose_tile(tile:Vector2i, selected, surrounding) -> Array:
var surrounding_tiles:Array = [] var surrounding_tiles:Array = []
@ -37,14 +44,15 @@ func choose_tile(tile:Vector2i, selected, surrounding) -> Array:
if selected_tile == null: if selected_tile == null:
tile_coords = Globals.td[selected].get("default")[0] tile_coords = Globals.td[selected].get("default")[0]
elif selected_tile.size() > 1: elif selected_tile.size() > 1:
tile_coords = Globals.choose_randomly(selected_tile) tile_coords = choose_randomly(selected_tile)
else: else:
tile_coords = selected_tile[0] tile_coords = selected_tile[0]
return [ return [
tile_coords, tile_coords,
0 if selected_tile else Globals.choose_randomly([0,1,2,3]) 0 if selected_tile else choose_randomly([0,1,2,3])
] ]
# Generates biomes, like forest and bog # Generates biomes, like forest and bog
func generate_biomes() -> void: func generate_biomes() -> void:
@ -80,6 +88,7 @@ func generate_biomes() -> void:
if noise_sample < 0.1: if noise_sample < 0.1:
Globals.map_terrain_data[y][x] = Globals.TILE_FOREST Globals.map_terrain_data[y][x] = Globals.TILE_FOREST
# can add other tresholds here for other biomes # can add other tresholds here for other biomes
func generate_world(filename) -> bool: func generate_world(filename) -> bool:
var image_size:Vector2i var image_size:Vector2i
@ -106,29 +115,30 @@ func generate_world(filename) -> bool:
var start = Time.get_ticks_usec() var start = Time.get_ticks_usec()
read_image_pixel_data() read_image_pixel_data()
var end = Time.get_ticks_usec() var end = Time.get_ticks_usec()
print("read image data ", (end-start)/1000.0, "ms") print("1/5: read image data ", (end-start)/1000.0, "ms")
start = Time.get_ticks_usec() start = Time.get_ticks_usec()
smooth_land_features(Globals.TILE_WATER) # smooth water smooth_land_features(Globals.TILE_WATER) # smooth water
end = Time.get_ticks_usec() end = Time.get_ticks_usec()
print("smooth water ", (end-start)/1000.0, "ms") print("2/5: smooth water ", (end-start)/1000.0, "ms")
start = Time.get_ticks_usec() start = Time.get_ticks_usec()
generate_biomes() generate_biomes()
end = Time.get_ticks_usec() end = Time.get_ticks_usec()
print("generate biomes ", (end-start)/1000.0, "ms") print("3/5: generate biomes ", (end-start)/1000.0, "ms")
start = Time.get_ticks_usec() start = Time.get_ticks_usec()
smooth_land_features(Globals.TILE_FOREST) # smooth out forest smooth_land_features(Globals.TILE_FOREST) # smooth out forest
end = Time.get_ticks_usec() end = Time.get_ticks_usec()
print("smooth forest ", (end-start)/1000.0, "ms") print("4/5: smooth forest ", (end-start)/1000.0, "ms")
start = Time.get_ticks_usec() start = Time.get_ticks_usec()
select_tilemap_tiles() select_tilemap_tiles()
end = Time.get_ticks_usec() end = Time.get_ticks_usec()
print("select tiles ", (end-start)/1000.0, "ms") print("5/5: select tiles ", (end-start)/1000.0, "ms")
return true return true
func read_image_pixel_data(): func read_image_pixel_data():
# initialize the array to have enough rows # initialize the array to have enough rows
@ -145,23 +155,25 @@ func read_image_pixel_data():
if image.get_pixel(x, y) == Globals.WATER_TILE_COLOR_IN_MAP_FILE: if image.get_pixel(x, y) == Globals.WATER_TILE_COLOR_IN_MAP_FILE:
Globals.map_terrain_data[y][x] = Globals.TILE_WATER Globals.map_terrain_data[y][x] = Globals.TILE_WATER
else: else:
Globals.map_terrain_data[y][x] = Globals.TILE_TERRAIN Globals.map_terrain_data[y][x] = Globals.TILE_TERRAIN
func select_tilemap_tiles() -> void: func select_tilemap_tiles() -> void:
for y in Globals.map_terrain_data.size(): for y in Globals.map_terrain_data.size():
for x in Globals.map_terrain_data[y].size(): for x in Globals.map_terrain_data[y].size():
# layer | position coords | tilemap id | coords of the tile at tilemap | alternative tile # layer | position coords | tilemap id | coords of the tile at tilemap | alternative tile
match Globals.map_terrain_data[y][x]: match Globals.map_terrain_data[y][x]:
Globals.TILE_WATER: # water or shoreline Globals.TILE_WATER: # water or shoreline
Globals.map_tile_data[y][x] = choose_tile(Vector2i(x, y), Globals.TILE_WATER, Globals.TILE_TERRAIN) Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_WATER, Globals.TILE_TERRAIN)
Globals.TILE_TERRAIN: #terrain or forest edge Globals.TILE_TERRAIN: #terrain or forest edge
Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_TERRAIN, Globals.TILE_FOREST) Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_TERRAIN, Globals.TILE_FOREST)
Globals.TILE_FOREST: Globals.TILE_FOREST:
Globals.map_tile_data[y][x] = [Vector2i(5,1), Globals.choose_randomly([0,1,2,3])] Globals.map_tile_data[y][x] = [Vector2i(5,1), choose_randomly([0,1,2,3])]
_: #default _: #default
push_error("should be never here in worldgen!")
pass pass
@ -187,6 +199,7 @@ func smooth_land_features(tile_type:int) -> void:
Globals.TILE_FOREST, Globals.TILE_FOREST,
Globals.TILE_TERRAIN Globals.TILE_TERRAIN
) )
# TEMP SPAGHETTI SOLUTION # TEMP SPAGHETTI SOLUTION
func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void: func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void:
@ -222,7 +235,8 @@ func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void:
_: _:
return return
#smooth_forest_recursively(pos, selected, comp) smooth_forest_recursively(pos, selected, comp)
func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void: func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void:
# now we are supposed to be inspecting a tile with land # now we are supposed to be inspecting a tile with land
@ -256,6 +270,7 @@ func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void:
smooth_recursively(pos, selected, comp) smooth_recursively(pos, selected, comp)
func validate_mapgen_params() -> bool: func validate_mapgen_params() -> bool:
if !are_coords_valid( if !are_coords_valid(
Globals.map_size, Globals.map_size,