From b4a2bb6144e2184aa07ee8b4620d4d948dc54903 Mon Sep 17 00:00:00 2001 From: Overside srl Date: Thu, 14 May 2026 12:29:41 +0200 Subject: [PATCH] biome generation optimization --- core/biome_generator/biome_generator.gd | 96 +++++++++++++------------ 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index 4e50efb..61da281 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -4,9 +4,18 @@ const CHUNK_TYPE_BIOME: int = 0 const CHUNK_TYPE_STRAIGHT_TRACK: int = 1 const CHUNK_TYPE_CURVED_TRACK: int = 2 -const CHUNK_GENERATION_STEPS_PER_FRAME: int = 2 -const CHUNK_CLEANUP_STEPS_PER_FRAME: int = 4 -const LAMPPOST_WIRE_STEPS_PER_FRAME: int = 1 +const CHUNK_GENERATION_FRAME_BUDGET_USEC: int = 2000 #2 ms/frame to generate chunks +const CHUNK_CLEANUP_FRAME_BUDGET_USEC: int = 1000 #1 ms/frame per cleanup +const LAMPPOST_WIRE_FRAME_BUDGET_USEC: int = 1000 #1 ms/frame per i fili dei lampioni +#(about 4 ms o work for frame) +#Se compaiono chunk troppo lentamente davanti al treno: +#aumentare generazione a 3000 o 4000 +#Se ci sono micro-scatti: +#abbassare generazione a 1000 o 1500 +#Se i fili appaiono in ritardo: +#aumentare wire a 1500 o 2000 +#Se il cleanup causa scatti: +#abbassare cleanup a 500 const MAX_CHUNK_UNIQUENESS: int = 5 const RAILWAY_SCENE_DIRECTORY: String = "res://tgcc/chunk/railway/scene" @@ -50,6 +59,9 @@ var prop_candidate_cache: Dictionary = {} var pending_generation_cells: Array[Vector2i] = [] var pending_cleanup_cells: Array[Vector2i] = [] var pending_wire_cells: Array[Vector2i] = [] +var pending_generation_cursor: int = 0 +var pending_cleanup_cursor: int = 0 +var pending_wire_cursor: int = 0 var pending_wire_lookup: Dictionary = {} var rail_chunk_catalogue: Dictionary = {} #rails chunk list @@ -132,7 +144,7 @@ func _destroy_and_regenrate_world() -> void: _refresh_world(current_pos) func _process(_delta: float) -> void: - #based on constant values the queue are evalutated by frame and not all together + #based on time budgets the queues are evaluated by frame and not all together _drain_cleanup_queue() _drain_generation_queue() _drain_wire_queue() @@ -283,6 +295,9 @@ func _clear_pending_world_work() -> void: pending_generation_cells.clear() pending_cleanup_cells.clear() pending_wire_cells.clear() + pending_generation_cursor = 0 + pending_cleanup_cursor = 0 + pending_wire_cursor = 0 pending_wire_lookup.clear() func _collect_replaceable_rail_chunks(root: Node, result: Array[Node3D]) -> void: @@ -384,6 +399,7 @@ func _refresh_world(center: Vector2i) -> void: #Use maxi(abs(x), abs(z)) to know the border of the ring. Cells closest to the train have more priority func _rebuild_generation_queue(center: Vector2i) -> void: pending_generation_cells.clear() + pending_generation_cursor = 0 for radius in range(eye_line + 1): for x in range(-radius, radius + 1): @@ -401,6 +417,7 @@ func _rebuild_generation_queue(center: Vector2i) -> void: #Persistent obstacle cells are not deleted (rails or set_pieces) func _rebuild_cleanup_queue(center: Vector2i) -> void: pending_cleanup_cells.clear() + pending_cleanup_cursor = 0 var safe_margin = 2 for grid_pos in board.keys(): @@ -413,10 +430,16 @@ func _rebuild_cleanup_queue(center: Vector2i) -> void: if dist_x > eye_line + safe_margin or dist_z > eye_line + safe_margin: pending_cleanup_cells.append(grid_pos) +#Check if a queue can continue to work based on time budget setted +func _queue_has_frame_budget(start_usec: int, budget_usec: int, processed: int) -> bool: + return processed == 0 or Time.get_ticks_usec() - start_usec < budget_usec + func _drain_generation_queue() -> void: - var processed = 0 - while processed < CHUNK_GENERATION_STEPS_PER_FRAME and not pending_generation_cells.is_empty(): - var grid_pos = pending_generation_cells.pop_front() + var processed: int = 0 + var start_usec: int = Time.get_ticks_usec() + while pending_generation_cursor < pending_generation_cells.size() and _queue_has_frame_budget(start_usec, CHUNK_GENERATION_FRAME_BUDGET_USEC, processed): + var grid_pos: Vector2i = pending_generation_cells[pending_generation_cursor] + pending_generation_cursor += 1 if board.has(grid_pos): continue @@ -425,10 +448,16 @@ func _drain_generation_queue() -> void: _add_compatible_biome(grid_pos) processed += 1 + if pending_generation_cursor >= pending_generation_cells.size(): + pending_generation_cells.clear() + pending_generation_cursor = 0 + func _drain_cleanup_queue() -> void: - var processed = 0 - while processed < CHUNK_CLEANUP_STEPS_PER_FRAME and not pending_cleanup_cells.is_empty(): - var grid_pos = pending_cleanup_cells.pop_front() + var processed: int = 0 + var start_usec: int = Time.get_ticks_usec() + while pending_cleanup_cursor < pending_cleanup_cells.size() and _queue_has_frame_budget(start_usec, CHUNK_CLEANUP_FRAME_BUDGET_USEC, processed): + var grid_pos: Vector2i = pending_cleanup_cells[pending_cleanup_cursor] + pending_cleanup_cursor += 1 if not board.has(grid_pos): continue @@ -441,6 +470,10 @@ func _drain_cleanup_queue() -> void: board.erase(grid_pos) processed += 1 + if pending_cleanup_cursor >= pending_cleanup_cells.size(): + pending_cleanup_cells.clear() + pending_cleanup_cursor = 0 + func _queue_lamppost_wire_connection(grid_pos: Vector2i) -> void: if pending_wire_lookup.has(grid_pos): return @@ -448,9 +481,11 @@ func _queue_lamppost_wire_connection(grid_pos: Vector2i) -> void: pending_wire_cells.append(grid_pos) func _drain_wire_queue() -> void: - var processed = 0 - while processed < LAMPPOST_WIRE_STEPS_PER_FRAME and not pending_wire_cells.is_empty(): - var grid_pos = pending_wire_cells.pop_front() + var processed: int = 0 + var start_usec: int = Time.get_ticks_usec() + while pending_wire_cursor < pending_wire_cells.size() and _queue_has_frame_budget(start_usec, LAMPPOST_WIRE_FRAME_BUDGET_USEC, processed): + var grid_pos: Vector2i = pending_wire_cells[pending_wire_cursor] + pending_wire_cursor += 1 pending_wire_lookup.erase(grid_pos) if not board.has(grid_pos): @@ -461,6 +496,10 @@ func _drain_wire_queue() -> void: _connect_lamppost_wires(grid_pos) processed += 1 + if pending_wire_cursor >= pending_wire_cells.size(): + pending_wire_cells.clear() + pending_wire_cursor = 0 + func _generate_pieces_around_train(center: Vector2i) -> void: for x in range(-eye_line, eye_line + 1): for z in range(-eye_line, eye_line + 1): @@ -1041,37 +1080,6 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void: p_sx_your_best = closest_sx[i_t] p_dx_your_best = closest_dx[i_t] - if best_closest_to_root == null: - for x in range(-ray_research, ray_research + 1): - for z in range(-ray_research, ray_research + 1): - if x == 0 and z == 0: continue - - var closest_pos = new_board_pos + Vector2i(x, z) - if board.has(closest_pos) and board[closest_pos].get("have_lamppost", false): - var closest_root = board[closest_pos]["node"] - var closest_info = board[closest_pos]["info"] - - if not is_instance_valid(closest_root) or closest_root == new_root or closest_info == null: continue - - if closest_info is Node3D: closest_info.force_update_transform() - var closest_sx = _get_lampposts(closest_info, "sx") - var closest_dx = _get_lampposts(closest_info, "dx") - if closest_sx.is_empty() or closest_dx.is_empty(): continue - - for i_m in range(new_sx.size()): - for i_t in range(closest_sx.size()): - var c_mio = (new_sx[i_m].global_position + new_dx[i_m].global_position) / 2.0 - var c_tuo = (closest_sx[i_t].global_position + closest_dx[i_t].global_position) / 2.0 - var dist = c_mio.distance_to(c_tuo) - - if dist < best_distance: - best_distance = dist - best_closest_to_root = closest_root - p_sx_my_best = new_sx[i_m] - p_dx_my_best = new_dx[i_m] - p_sx_your_best = closest_sx[i_t] - p_dx_your_best = closest_dx[i_t] - var max_dist = chunk_size * lamppost_dist_factor if best_closest_to_root != null and best_distance < max_dist: