WIP checkpoint: Implement chunk streaming system in GDScript
This commit is contained in:
+81
-58
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user