Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add threaded chunk loaders and renderers #116

Merged
merged 18 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1b1dd01
Implemented ThreadedChunkLoader3D
cullumi May 30, 2024
67716f0
Implemented ThreadedChunkLoader2D
cullumi May 30, 2024
092bebf
Added super call to _process in threaded chunk loaders (so they actua…
cullumi May 30, 2024
de09efd
Added checkbox for turning on and off threading for ThreadedChunkLoaders
cullumi May 30, 2024
7272aef
Added a Thread Wrapper Template
cullumi May 31, 2024
551e931
Implemented ThreadedTilemapGaeaRenderer
cullumi May 31, 2024
afed6ea
Added ThreadWrapperQueue script template
cullumi May 31, 2024
1c42f7b
Implemented ThreadedGridmapGaeaRenderer.
cullumi May 31, 2024
c2faec4
Threaded Gridmap now runs render tasks in parallel.
cullumi May 31, 2024
6024a85
Threaded Tilemap and Gridmap Gaea Renderers have a queue again; suppo…
cullumi May 31, 2024
dac4118
Actor's gravity can be turned off (gets turned on after a delay in th…
cullumi May 31, 2024
bc099dc
Fixed "_octant_update: Condition "!cell_map.has(E) is true. Continuin…
cullumi May 31, 2024
d6ee4ed
A -1 max_running value in Threaded Gridmap and Tilemap Gaea Renderers…
cullumi May 31, 2024
aa080cf
Fixed a bug when running queued render tasks; infinite while loop.
cullumi May 31, 2024
b2b0e68
Added Multithreaded Wrapper script template.
cullumi May 31, 2024
325742b
Added descriptions and improved naming conventions for variables in T…
cullumi Jun 6, 2024
65cab13
Default value for Threaded Gridmap Gaea Renderer is now -1.
cullumi Jun 6, 2024
51270ab
Clarified the node names in the chunks_heightmap_demo and the heightm…
cullumi Jun 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions addons/gaea/generators/2D/chunk_aware_generator_2d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func erase_chunk(chunk_position: Vector2i) -> void:
for layer in grid.get_layer_count():
grid.erase(Vector2i(x, y), layer)

chunk_updated.emit(chunk_position)
chunk_erased.emit(chunk_position)
(func(): chunk_updated.emit(chunk_position)).call_deferred() # deferred for threadability
(func(): chunk_erased.emit(chunk_position)).call_deferred() # deferred for threadability


func _apply_modifiers_chunk(modifiers: Array[Modifier2D], chunk_position: Vector2i) -> void:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func generate_chunk(chunk_position: Vector2i, starting_grid: GaeaGrid = null) ->
next_pass.generate_chunk(chunk_position, grid)
return

chunk_updated.emit(chunk_position)
chunk_generation_finished.emit(chunk_position)
(func(): chunk_updated.emit(chunk_position)).call_deferred() # deferred for threadability
(func(): chunk_generation_finished.emit(chunk_position)).call_deferred() # deferred for threadability


func _set_grid() -> void:
Expand Down
4 changes: 2 additions & 2 deletions addons/gaea/generators/3D/chunk_aware_generator_3d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func erase_chunk(chunk_position: Vector3i) -> void:
for layer in range(grid.get_layer_count()):
grid.erase(Vector3i(x, y, z), layer)

chunk_updated.emit(chunk_position)
chunk_erased.emit(chunk_position)
(func(): chunk_updated.emit(chunk_position)).call_deferred() # deferred for threadability
(func(): chunk_erased.emit(chunk_position)).call_deferred() # deferred for threadability


func _apply_modifiers_chunk(modifiers, chunk_position: Vector3i) -> void:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func generate_chunk(chunk_position: Vector3i, starting_grid: GaeaGrid = null) ->
next_pass.generate_chunk(chunk_position, grid)
return

chunk_updated.emit(chunk_position)
chunk_generation_finished.emit(chunk_position)
(func(): chunk_updated.emit(chunk_position)).call_deferred() # deferred for threadability
(func(): chunk_generation_finished.emit(chunk_position)).call_deferred() # deferred for threadability


func _set_grid() -> void:
Expand Down
2 changes: 2 additions & 0 deletions addons/gaea/others/chunk_loader_3d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func _ready() -> void:


func _process(delta: float) -> void:

if Engine.is_editor_hint() or not is_instance_valid(generator):
return

Expand Down Expand Up @@ -69,6 +70,7 @@ func _try_loading() -> void:

# loads needed chunks around the given position
func _update_loading(actor_position: Vector3i) -> void:

if generator == null:
push_error("Chunk loading failed because generator property not set!")
return
Expand Down
33 changes: 33 additions & 0 deletions addons/gaea/others/threaded_chunk_loader_2d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tool
class_name ThreadedChunkLoader2D
extends ChunkLoader2D
## @experimental

@export var threaded:bool = true

var queued:Callable
var task:int = -1

func _process(_delta):
if task > -1:
if WorkerThreadPool.is_task_completed(task):
WorkerThreadPool.wait_for_task_completion(task)
task = -1
run_job(queued)
super(_delta)

func _update_loading(actor_position: Vector2i) -> void:
if not threaded:
super(actor_position)
else:
var job:Callable = func ():
super._update_loading(actor_position)

if task > -1:
queued = job
else:
run_job(job)

func run_job(job:Callable):
if job:
task = WorkerThreadPool.add_task(job, false, "Load/Unload Chunks")
33 changes: 33 additions & 0 deletions addons/gaea/others/threaded_chunk_loader_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tool
class_name ThreadedChunkLoader3D
extends ChunkLoader3D
## @experimental

@export var threaded:bool = true

var queued:Callable
var task:int = -1

func _process(_delta):
if task > -1:
if WorkerThreadPool.is_task_completed(task):
WorkerThreadPool.wait_for_task_completion(task)
task = -1
run_job(queued)
super(_delta)

func _update_loading(actor_position: Vector3i) -> void:
if not threaded:
super(actor_position)
else:
var job:Callable = func ():
super._update_loading(actor_position)

if task > -1:
queued = job
else:
run_job(job)

func run_job(job:Callable):
if job:
task = WorkerThreadPool.add_task(job, false, "Load/Unload Chunks")
42 changes: 42 additions & 0 deletions addons/gaea/renderers/2D/threaded_tilemap_gaea_renderer.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@tool
class_name ThreadedTilemapGaeaRenderer
extends TilemapGaeaRenderer
## Wrapper for TilemapGaeaRenderer that runs multiple _draw_area calls
## in parallel using the WorkerThreadPool.
## @experimental

## Whether or not to pass calls through to the default TilemapGaeaRenderer,
## instead of threading them.
@export var threaded:bool = true
## Decides the maximum number of WorkerThreadPool tasks that can be created
## before queueing new tasks. A negative value (-1) means there is no limit.
@export_range(-1, 1000, 1, "exp", "or_greater") var task_limit:int = -1

var queued:Array[Callable] = []
var tasks:PackedInt32Array = []

func _process(_delta):
for t in range(tasks.size()-1, -1, -1):
if WorkerThreadPool.is_task_completed(tasks[t]):
WorkerThreadPool.wait_for_task_completion(tasks[t])
tasks.remove_at(t)
if threaded:
while task_limit >= 0 and tasks.size() < task_limit and not queued.is_empty():
run_task(queued.pop_front())
#super(_delta) # not needed for TilemapGaeaRenderer

func _draw_area(area: Rect2i) -> void:
if not threaded:
super(area)
else:
var new_task:Callable = func ():
super._draw_area(area)

if task_limit >= 0 and tasks.size() >= task_limit:
queued.push_back(new_task)
else:
run_task(new_task)

func run_task(task:Callable):
if task:
tasks.append(WorkerThreadPool.add_task(task, false, "Draw Area"))
8 changes: 4 additions & 4 deletions addons/gaea/renderers/2D/tilemap_gaea_renderer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func _draw_area(area: Rect2i) -> void:

if erase_empty_tiles and not has_cell_in_position:
for l in range(tile_map.get_layers_count()):
tile_map.erase_cell(l, Vector2i(x, y))
tile_map.erase_cell.call_deferred(l, Vector2i(x, y))
continue

for layer in range(generator.grid.get_layer_count()):
Expand All @@ -41,7 +41,7 @@ func _draw_area(area: Rect2i) -> void:

match tile_info.type:
TilemapTileInfo.Type.SINGLE_CELL:
tile_map.set_cell(
tile_map.set_cell.call_deferred(
tile_info.tilemap_layer, tile, tile_info.source_id,
tile_info.atlas_coord, tile_info.alternative_tile
)
Expand All @@ -52,12 +52,12 @@ func _draw_area(area: Rect2i) -> void:
terrains[tile_info].append(tile)

for tile_info in terrains:
tile_map.set_cells_terrain_connect(
tile_map.set_cells_terrain_connect.call_deferred(
tile_info.tilemap_layer, terrains[tile_info],
tile_info.terrain_set, tile_info.terrain
)

area_rendered.emit()
(func(): area_rendered.emit()).call_deferred()


func _draw() -> void:
Expand Down
6 changes: 3 additions & 3 deletions addons/gaea/renderers/3D/gridmap_gaea_renderer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func _draw_area(area: AABB) -> void:
for layer in range(generator.grid.get_layer_count()):
var cell := Vector3i(x, y, z)
if not generator.grid.has_cell(cell, layer):
grid_map.set_cell_item(cell, -1)
grid_map.set_cell_item.call_deferred(cell, -1)
continue

if only_draw_visible_cells and not generator.grid.has_empty_neighbor(cell, layer):
Expand All @@ -25,9 +25,9 @@ func _draw_area(area: AABB) -> void:
if not (tile_info is GridmapTileInfo):
continue

grid_map.set_cell_item(cell, tile_info.index)
grid_map.set_cell_item.call_deferred(cell, tile_info.index)

area_rendered.emit()
(func(): area_rendered.emit()).call_deferred()


func _draw() -> void:
Expand Down
42 changes: 42 additions & 0 deletions addons/gaea/renderers/3D/threaded_gridmap_gaea_renderer.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@tool
class_name ThreadedGridmapGaeaRenderer
extends GridmapGaeaRenderer
## Wrapper for GridmapGaeaRenderer that runs multiple _draw_area calls
## in parallel using the WorkerThreadPool.
## @experimental

## Whether or not to pass calls through to the default GridmapGaeaRenderer,
## instead of threading them.
@export var threaded:bool = true
## Decides the maximum number of WorkerThreadPool tasks that can be created
## before queueing new tasks. A negative value (-1) means there is no limit.
@export var task_limit:int = -1

var queued:Array[Callable] = []
var tasks:PackedInt32Array = []

func _process(_delta):
for t in range(tasks.size()-1, -1, -1):
if WorkerThreadPool.is_task_completed(tasks[t]):
WorkerThreadPool.wait_for_task_completion(tasks[t])
tasks.remove_at(t)
if threaded:
while task_limit >= 0 and tasks.size() < task_limit and not queued.is_empty():
run_task(queued.pop_front())
#super(_delta) # not needed for TilemapGaeaRenderer

func _draw_area(area: AABB) -> void:
if not threaded:
super(area)
else:
var new_task:Callable = func ():
super._draw_area(area)

if task_limit >= 0 and tasks.size() >= task_limit:
queued.push_back(new_task)
else:
run_task(new_task)

func run_task(task:Callable):
if task:
tasks.append(WorkerThreadPool.add_task(task, false, "Draw Area"))
13 changes: 6 additions & 7 deletions scenes/demos/heightmap/chunks_heightmap_demo.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
[ext_resource type="Resource" uid="uid://db18nmw5jkbtc" path="res://scenes/demos/heightmap/terraria-like_generation_settings.tres" id="2_py0je"]
[ext_resource type="Texture2D" uid="uid://caoyt0ek7fmdq" path="res://scenes/demos/heightmap/pixel-platformer-tileset - Kenney.png" id="3_wjapt"]
[ext_resource type="Texture2D" uid="uid://6gwie8e2kpg2" path="res://scenes/demos/heightmap/ores.png" id="4_rk2l5"]
[ext_resource type="Script" path="res://addons/gaea/renderers/2D/tilemap_gaea_renderer.gd" id="10_5yyno"]
[ext_resource type="Script" path="res://addons/gaea/others/threaded_chunk_loader_2d.gd" id="5_4wj8l"]
[ext_resource type="Script" path="res://addons/gaea/renderers/2D/threaded_tilemap_gaea_renderer.gd" id="6_gb7up"]
[ext_resource type="Script" path="res://scenes/demos/heightmap/test_actor.gd" id="10_76aeg"]
[ext_resource type="Script" path="res://addons/gaea/others/chunk_loader_2d.gd" id="10_vlq7b"]
[ext_resource type="Texture2D" uid="uid://bh3d1rgohpb4x" path="res://icon.svg" id="11_go0dd"]

[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_68jc8"]
Expand Down Expand Up @@ -231,16 +231,15 @@ offset_right = 2304.0
offset_bottom = 18.0
color = Color(0.172549, 0.188235, 0.243137, 1)

[node name="ChunkLoader" type="Node2D" parent="." node_paths=PackedStringArray("generator", "renderer", "actor")]
script = ExtResource("10_vlq7b")
[node name="ThreadedChunkLoader2D" type="Node2D" parent="." node_paths=PackedStringArray("generator", "actor")]
script = ExtResource("5_4wj8l")
generator = NodePath("../HeightmapGenerator2D")
renderer = NodePath("../TilemapGaeaRenderer")
actor = NodePath("../TestActor")
loading_radius = Vector2i(4, 4)
update_rate = 2

[node name="TilemapGaeaRenderer" type="Node" parent="." node_paths=PackedStringArray("tile_map", "generator")]
script = ExtResource("10_5yyno")
[node name="ThreadedTilemapGaeaRenderer" type="Node" parent="." node_paths=PackedStringArray("tile_map", "generator")]
script = ExtResource("6_gb7up")
tile_map = NodePath("../TileMap")
generator = NodePath("../HeightmapGenerator2D")

Expand Down
7 changes: 7 additions & 0 deletions scenes/demos/heightmap_3d/actor.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5

@export var gravity_on:bool = true :
set(value):
gravity_on = value
toggle_gravity(value)

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")

func toggle_gravity(on:bool=true):
gravity = 0 if not on else ProjectSettings.get_setting("physics/3d/default_gravity")

func _physics_process(delta: float) -> void:
# Add the gravity.
Expand Down
24 changes: 17 additions & 7 deletions scenes/demos/heightmap_3d/heightmap_3d_demo.tscn

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions script_templates/Node/multithread_wrapper.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@tool
class_name MultiThreadedNode
extends Node
## @experimental

@export var threaded:bool = true
@export var max_running:int = 1

var queued:Array[Callable] = []
var tasks:PackedInt32Array = []

func _process(_delta):
for t in range(tasks.size()-1, -1, -1):
if WorkerThreadPool.is_task_completed(tasks[t]):
WorkerThreadPool.wait_for_task_completion(tasks[t])
tasks.remove_at(t)
if threaded:
while max_running >= 0 and tasks.size() < max_running and not queued.is_empty():
run_job(queued.pop_front())
#super(_delta) # not needed for TilemapGaeaRenderer

func _some_method(some_value) -> void:
if not threaded:
super(some_value)
else:
var job:Callable = func ():
super._some_method(area)

if max_running >= 0 and tasks.size() >= max_running:
queued.push_back(job)
else:
run_job(job)

func run_job(job:Callable):
if job:
tasks.append(WorkerThreadPool.add_task(job, false, "Some Threaded Job"))
33 changes: 33 additions & 0 deletions script_templates/Node/thread_wrapper.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tool
class_name ThreadedNode
extends Node
## @experimental

@export var threaded:bool = true

var queued:Callable
var task:int = -1

func _process(_delta):
if task > -1:
if WorkerThreadPool.is_task_completed(task):
WorkerThreadPool.wait_for_task_completion(task)
task = -1
run_job(queued)
super(_delta)

func _some_method(some_value) -> void:
if not threaded:
super(some_value)
else:
var job:Callable = func ():
super._some_method(some_value)

if task > -1:
queued = job
else:
run_job(job)

func run_job(job:Callable):
if job:
task = WorkerThreadPool.add_task(job, false, "Some Threaded Job")
Loading