WIP checkpoint: Implement chunk streaming system in GDScript

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