Address review feedback: Implement chunk streaming system in GDScript #20

Merged
reviewer-bot merged 21 commits from agent/bd718b31b595 into main 2026-08-18 14:41:08 +00:00
Showing only changes of commit 62172e0ee4 - Show all commits
+81 -58
View File
@@ -1,82 +1,105 @@
class_name Chunk class_name Chunk
extends Node2D extends Node3D
## A single loaded chunk in the world. ## A single world chunk. Holds the chunk coordinate, bounds, and the
## visual/gameplay content for that region of the world.
## ##
## A chunk is a fixed-size, axis-aligned region of a room. It is the atomic ## Chunks are the atomic unit of world geometry, gameplay objects, and
## unit of load, save, and stream. This node is instantiated by the ## persistence (see docs/architecture/world.md). The server loads and saves
## [ChunkStreamer] when a chunk is loaded from the server and freed when the ## whole chunks; clients stream the chunks around them.
## chunk is unloaded.
## World-space size of a chunk along X (world units / tiles). ## Default chunk dimensions in world units (tiles). Matches the constants in
const CHUNK_SIZE_X := 32.0 ## docs/architecture/world.md section 2.2.
## World-space size of a chunk along Y (world units / tiles). const CHUNK_SIZE_X: int = 32
const CHUNK_SIZE_Y := 32.0 const CHUNK_SIZE_Y: int = 32
## World-space size of a chunk along Z (world units / tiles). const CHUNK_SIZE_Z: int = 1
const CHUNK_SIZE_Z := 1.0
## Canonical chunk key: "{world_id}:{room_id}:{cx}:{cy}:{cz}". ## World-space origin of chunk coordinate (0,0).
const CHUNK_ORIGIN := Vector3(0.0, 0.0, 0.0)
## Chunk coordinate (cx, cy, cz) in chunk space.
var coord := Vector3i.ZERO
## The canonical chunk key: "{world_id}:{room_id}:{cx}:{cy}:{cz}".
var key: String = "" var key: String = ""
## World id this chunk belongs to.
## World and room identifiers this chunk belongs to.
var world_id: String = "" var world_id: String = ""
## Room id this chunk belongs to.
var room_id: String = "" var room_id: String = ""
## Integer chunk coordinate along X.
var cx: int = 0
## Integer chunk coordinate along Y.
var cy: int = 0
## Integer chunk coordinate along Z.
var cz: int = 0
## Whether this chunk is currently loaded and active in the scene tree. ## Format version of the chunk payload (currently 1).
var is_loaded := false var format_version: int = 1
## Emitted when the chunk has finished loading its content. ## Revision counter, incremented on every write. Used for optimistic
signal chunk_loaded(chunk: Chunk) ## concurrency and change detection.
## Emitted when the chunk is about to be unloaded. var revision: int = 0
signal chunk_unloaded(chunk: Chunk)
## Whether this chunk has been modified in memory but not yet flushed.
var dirty: bool = false
## Container for static objects (props, geometry) in this chunk.
var objects: Array[Node3D] = []
## Container for dynamic object templates (spawnable entities).
var dynamic_templates: Array[Node3D] = []
## The world-space bounding box of this chunk.
var bounds: AABB
## Build the canonical chunk key from the chunk's coordinates. func _init(p_coord: Vector3i = Vector3i.ZERO, p_world_id: String = "", p_room_id: String = "") -> void:
func build_key() -> String: coord = p_coord
return "%s:%s:%d:%d:%d" % [world_id, room_id, cx, cy, cz] world_id = p_world_id
room_id = p_room_id
key = build_key(world_id, room_id, coord)
bounds = compute_bounds(coord)
## Compute the world-space origin (min corner) of this chunk. ## Build the canonical chunk key from its components.
func world_origin() -> Vector2: static func build_key(p_world_id: String, p_room_id: String, p_coord: Vector3i) -> String:
return Vector2(cx * CHUNK_SIZE_X, cy * CHUNK_SIZE_Y) return "%s:%s:%d:%d:%d" % [p_world_id, p_room_id, p_coord.x, p_coord.y, p_coord.z]
## Compute the world-space bounding box of this chunk. ## Compute the world-space bounding box for a chunk coordinate.
func world_bounds() -> Rect2: static func compute_bounds(p_coord: Vector3i) -> AABB:
var origin := world_origin() var min_pos := Vector3(
return Rect2(origin, Vector2(CHUNK_SIZE_X, CHUNK_SIZE_Y)) CHUNK_ORIGIN.x + p_coord.x * CHUNK_SIZE_X,
CHUNK_ORIGIN.y + p_coord.y * CHUNK_SIZE_Y,
CHUNK_ORIGIN.z + p_coord.z * CHUNK_SIZE_Z
)
return AABB(min_pos, Vector3(CHUNK_SIZE_X, CHUNK_SIZE_Y, CHUNK_SIZE_Z))
## Compute the integer chunk coordinate for a world-space position. ## Compute the chunk coordinate that contains a world-space position.
static func coord_for_position(pos: Vector2) -> Vector2i: static func coord_for_position(p_pos: Vector3) -> Vector3i:
return Vector2i( return Vector3i(
floori(pos.x / CHUNK_SIZE_X), int(floor((p_pos.x - CHUNK_ORIGIN.x) / CHUNK_SIZE_X)),
floori(pos.y / CHUNK_SIZE_Y) int(floor((p_pos.y - CHUNK_ORIGIN.y) / CHUNK_SIZE_Y)),
int(floor((p_pos.z - CHUNK_ORIGIN.z) / CHUNK_SIZE_Z))
) )
## Compute the canonical key for a world-space position. ## Add a static object to this chunk.
static func key_for_position(pos: Vector2, p_world_id: String, p_room_id: String) -> String: func add_object(p_object: Node3D) -> void:
var c := coord_for_position(pos) objects.append(p_object)
return "%s:%s:%d:%d:0" % [p_world_id, p_room_id, c.x, c.y] add_child(p_object)
## Load this chunk's content. In a real deployment this would fetch the chunk ## Add a dynamic object template to this chunk.
## payload from the server (see world.md §5.3 `request_chunk` / `chunk_data`) func add_dynamic_template(p_template: Node3D) -> void:
## and instantiate tiles/objects. For now it just marks the chunk loaded. dynamic_templates.append(p_template)
func load_chunk() -> void: add_child(p_template)
is_loaded = true
chunk_loaded.emit(self)
## Unload this chunk's content and free any child nodes. ## Remove all objects from this chunk.
func unload_chunk() -> void: func clear_objects() -> void:
chunk_unloaded.emit(self) for obj in objects:
for child in get_children(): if is_instance_valid(obj):
child.queue_free() obj.queue_free()
is_loaded = false objects.clear()
dynamic_templates.clear()
## Mark this chunk as dirty (modified but not yet persisted).
func mark_dirty() -> void:
dirty = true
revision += 1