chore: update pathfinding, add rpc/world-streaming docs, refactor world-streaming location (#376)

This commit is contained in:
YHH
2025-12-28 19:18:28 +08:00
committed by GitHub
parent 838cda91aa
commit 0662b07445
44 changed files with 3850 additions and 165 deletions

View File

@@ -0,0 +1,103 @@
import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
import type { IChunkCoord, IChunkBounds } from '../types';
import { EChunkState } from '../types';
/**
* 区块组件
*
* Attached to chunk root entities. Tracks chunk state and bounds.
*
* 区块组件挂载在区块根实体上,用于管理区块状态和边界信息。
*/
@ECSComponent('Chunk')
@Serializable({ version: 1, typeId: 'Chunk' })
export class ChunkComponent extends Component {
@Serialize()
@Property({ type: 'integer', label: 'Coord X', readOnly: true })
private _coordX: number = 0;
@Serialize()
@Property({ type: 'integer', label: 'Coord Y', readOnly: true })
private _coordY: number = 0;
@Serialize()
@Property({ type: 'string', label: 'State', readOnly: true })
private _state: EChunkState = EChunkState.Unloaded;
@Serialize()
private _minX: number = 0;
@Serialize()
private _minY: number = 0;
@Serialize()
private _maxX: number = 0;
@Serialize()
private _maxY: number = 0;
private _lastAccessTime: number = 0;
get coord(): IChunkCoord {
return { x: this._coordX, y: this._coordY };
}
get bounds(): IChunkBounds {
return {
minX: this._minX,
minY: this._minY,
maxX: this._maxX,
maxY: this._maxY
};
}
get state(): EChunkState {
return this._state;
}
get lastAccessTime(): number {
return this._lastAccessTime;
}
/**
* 初始化区块
*
* Initialize chunk with coordinates and bounds.
*/
initialize(coord: IChunkCoord, bounds: IChunkBounds): void {
this._coordX = coord.x;
this._coordY = coord.y;
this._minX = bounds.minX;
this._minY = bounds.minY;
this._maxX = bounds.maxX;
this._maxY = bounds.maxY;
this._lastAccessTime = Date.now();
}
/**
* 设置区块状态
*
* Set chunk state.
*/
setState(state: EChunkState): void {
this._state = state;
}
/**
* 更新访问时间
*
* Update last access time for LRU tracking.
*/
touch(): void {
this._lastAccessTime = Date.now();
}
/**
* 检查点是否在区块内
*
* Check if a point is within chunk bounds.
*/
containsPoint(x: number, y: number): boolean {
return x >= this._minX && x < this._maxX && y >= this._minY && y < this._maxY;
}
}

View File

@@ -0,0 +1,133 @@
import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
import type { IChunkCoord, IStreamingConfig } from '../types';
import { DEFAULT_STREAMING_CONFIG } from '../types';
/**
* 区块加载器组件
*
* Singleton component that manages streaming configuration.
* Attach to a manager entity in the scene.
*
* 单例组件,管理流式加载配置。挂载在场景管理实体上。
*/
@ECSComponent('ChunkLoader')
@Serializable({ version: 1, typeId: 'ChunkLoader' })
export class ChunkLoaderComponent extends Component {
@Serialize()
@Property({ type: 'integer', label: 'Chunk Size', min: 64, max: 4096 })
private _chunkSize: number = DEFAULT_STREAMING_CONFIG.chunkSize;
@Serialize()
@Property({ type: 'integer', label: 'Load Radius', min: 1, max: 10 })
private _loadRadius: number = DEFAULT_STREAMING_CONFIG.loadRadius;
@Serialize()
@Property({ type: 'integer', label: 'Unload Radius', min: 2, max: 20 })
private _unloadRadius: number = DEFAULT_STREAMING_CONFIG.unloadRadius;
@Serialize()
@Property({ type: 'integer', label: 'Max Loads Per Frame', min: 1, max: 10 })
private _maxLoadsPerFrame: number = DEFAULT_STREAMING_CONFIG.maxLoadsPerFrame;
@Serialize()
@Property({ type: 'integer', label: 'Max Unloads Per Frame', min: 1, max: 10 })
private _maxUnloadsPerFrame: number = DEFAULT_STREAMING_CONFIG.maxUnloadsPerFrame;
@Serialize()
@Property({ type: 'integer', label: 'Unload Delay (ms)', min: 0, max: 30000 })
private _unloadDelay: number = DEFAULT_STREAMING_CONFIG.unloadDelay;
@Serialize()
@Property({ type: 'boolean', label: 'Enable Prefetch' })
private _bEnablePrefetch: boolean = DEFAULT_STREAMING_CONFIG.bEnablePrefetch;
@Serialize()
@Property({ type: 'integer', label: 'Prefetch Radius', min: 0, max: 5 })
private _prefetchRadius: number = DEFAULT_STREAMING_CONFIG.prefetchRadius;
get chunkSize(): number {
return this._chunkSize;
}
get loadRadius(): number {
return this._loadRadius;
}
get unloadRadius(): number {
return this._unloadRadius;
}
get maxLoadsPerFrame(): number {
return this._maxLoadsPerFrame;
}
get maxUnloadsPerFrame(): number {
return this._maxUnloadsPerFrame;
}
get unloadDelay(): number {
return this._unloadDelay;
}
get bEnablePrefetch(): boolean {
return this._bEnablePrefetch;
}
get prefetchRadius(): number {
return this._prefetchRadius;
}
/**
* 应用配置
*
* Apply streaming configuration.
*/
applyConfig(config: Partial<IStreamingConfig>): void {
if (config.chunkSize !== undefined) this._chunkSize = config.chunkSize;
if (config.loadRadius !== undefined) this._loadRadius = config.loadRadius;
if (config.unloadRadius !== undefined) this._unloadRadius = config.unloadRadius;
if (config.maxLoadsPerFrame !== undefined) this._maxLoadsPerFrame = config.maxLoadsPerFrame;
if (config.maxUnloadsPerFrame !== undefined) this._maxUnloadsPerFrame = config.maxUnloadsPerFrame;
if (config.unloadDelay !== undefined) this._unloadDelay = config.unloadDelay;
if (config.bEnablePrefetch !== undefined) this._bEnablePrefetch = config.bEnablePrefetch;
if (config.prefetchRadius !== undefined) this._prefetchRadius = config.prefetchRadius;
}
/**
* 世界坐标转区块坐标
*
* Convert world position to chunk coordinates.
*/
worldToChunk(worldX: number, worldY: number): IChunkCoord {
return {
x: Math.floor(worldX / this._chunkSize),
y: Math.floor(worldY / this._chunkSize)
};
}
/**
* 区块坐标转世界坐标(返回区块中心)
*
* Convert chunk coordinates to world position (chunk center).
*/
chunkToWorld(coord: IChunkCoord): { x: number; y: number } {
return {
x: coord.x * this._chunkSize + this._chunkSize * 0.5,
y: coord.y * this._chunkSize + this._chunkSize * 0.5
};
}
/**
* 获取区块边界
*
* Get chunk world-space bounds.
*/
getChunkBounds(coord: IChunkCoord): { minX: number; minY: number; maxX: number; maxY: number } {
return {
minX: coord.x * this._chunkSize,
minY: coord.y * this._chunkSize,
maxX: (coord.x + 1) * this._chunkSize,
maxY: (coord.y + 1) * this._chunkSize
};
}
}

View File

@@ -0,0 +1,92 @@
import { Component, ECSComponent, Serializable, Serialize, Property } from '@esengine/ecs-framework';
import type { IPositionable } from '@esengine/spatial';
import type { IVector2 } from '@esengine/ecs-framework-math';
/**
* 流式锚点组件
*
* Marks an entity as a streaming anchor point.
* Chunks are loaded/unloaded based on distance to anchors.
*
* 标记实体作为流式加载锚点。通常挂载在玩家或摄像机实体上,
* 系统会根据锚点位置加载/卸载周围区块。
*
* 用户需要在每帧更新此组件的 x/y 位置。
* User must update the x/y position each frame.
*/
@ECSComponent('StreamingAnchor')
@Serializable({ version: 1, typeId: 'StreamingAnchor' })
export class StreamingAnchorComponent extends Component implements IPositionable {
/**
* 当前 X 位置
*
* Current X position in world units.
*/
@Serialize()
@Property({ type: 'number', label: 'X' })
x: number = 0;
/**
* 当前 Y 位置
*
* Current Y position in world units.
*/
@Serialize()
@Property({ type: 'number', label: 'Y' })
y: number = 0;
/**
* 获取位置 (IPositionable 接口)
*
* Get position (IPositionable interface).
*/
get position(): IVector2 {
return { x: this.x, y: this.y };
}
/**
* 锚点权重
*
* Weight multiplier for this anchor's load radius.
* Higher values mean larger load radius around this anchor.
*/
@Serialize()
@Property({ type: 'number', label: 'Weight', min: 0.1, max: 10 })
weight: number = 1.0;
/**
* 是否启用预加载
*
* Enable directional prefetching based on movement.
*/
@Serialize()
@Property({ type: 'boolean', label: 'Enable Prefetch' })
bEnablePrefetch: boolean = true;
/**
* 上一帧位置 X
*
* Previous frame X position for velocity calculation.
*/
previousX: number = 0;
/**
* 上一帧位置 Y
*
* Previous frame Y position for velocity calculation.
*/
previousY: number = 0;
/**
* 速度 X 分量
*
* X component of velocity (units per second).
*/
velocityX: number = 0;
/**
* 速度 Y 分量
*
* Y component of velocity (units per second).
*/
velocityY: number = 0;
}

View File

@@ -0,0 +1,3 @@
export { ChunkComponent } from './ChunkComponent';
export { StreamingAnchorComponent } from './StreamingAnchorComponent';
export { ChunkLoaderComponent } from './ChunkLoaderComponent';