game_space_chunk_chunk_mgr.js
import Chunk from "./chunk.js";
/**
* @module ChunkMgr
* @fileoverview Contains ChunkMgr class.
*/
/**
* @class
* Manager of chunks for a space.
*/
class ChunkMgr {
/**
* Create a new chunk manager.
* @param {Space} space Parent space
* @param {number} chunkSize Number of units per chunk
* @param {number} unitSize Number of pixels per unit
* @constructor
*/
constructor(space, chunkSize, unitSize) {
this.space = space;
this.chunkSize = chunkSize;
this.unitSize = unitSize;
this.rx = Math.ceil($$.width / this.chunkSize / this.unitSize / 2);
this.ry = Math.ceil($$.height / this.chunkSize / this.unitSize / 2);
this.chunks = new Map();
this.count = 0;
this.counter = document.getElementById("chunk-count");
this.cleanUpTimer = 0;
this.cleanUpTimerStop = 4;
}
//
// Update & Draw
//
/**
* Update all chunks.
* @param {number} elapsed Time since last update cycle in seconds
*/
update(elapsed) {
if (this.space.camera === null)
return;
const zoom = this.space.camera.calcZoom();
const chunks = this.getNearby(this.space.camera.pos,
Math.ceil(this.rx / zoom), Math.ceil(this.ry / zoom));
//for (const chunk of chunks)
// chunk.update(elapsed);
//for (const chunk of chunks)
// chunk.addAllQueue();
this.forAll(chunk => chunk.update(elapsed));
this.cleanUpTimer += elapsed;
if (this.cleanUpTimer >= this.cleanUpTimerStop) {
this.cleanUpTimer = 0;
this._replaceAll(chunks);
}
if (this.counter)
this.counter.innerText = this.count;
}
/**
* Draw all chunks.
* @param {CanvasRenderingContext2D} ctx Canvas context to draw on
*/
draw(ctx) {
if (this.space.camera === null)
return;
const offset = this.space.camera.getOffsetWithZoom();
const zoom = this.space.camera.calcZoom();
ctx.save();
ctx.scale(zoom, zoom);
ctx.translate(-Math.floor(offset.x), -Math.floor(offset.y));
const chunks = this.getNearby(this.space.camera.pos,
Math.ceil(this.rx / zoom) + 1, Math.ceil(this.ry / zoom) + 1);
for (const chunk of chunks)
chunk.drawUnits(ctx);
for (const chunk of chunks)
chunk.drawActors(ctx);
if ($$.debug.chunks)
for (const chunk of chunks)
chunk.drawDebug(ctx);
this.space.events.do("draw", ctx);
ctx.restore();
}
//
// Actors
//
/**
* Add an actor to the space.
* @param {Actor} actor Actor to add
*/
addActor(actor) {
const index = this.getIndex(actor.pos);
this.valChunk(index.x, index.y);
this.valChunk(index.x, index.y).addActor(actor);
}
/**
* Get all actors near a specified position and within a radius.
* @param {Actor} ignore Actor to ignore
* @param {Vec2} pos Point to find actors around
* @param {number} radius Radius to find actors within
* @returns {Actor[]} Array of actors near point within at radius
*/
getNearbyActors(ignore, pos, radius) {
let result = [];
const r = Math.ceil(radius / (this.unitSize * this.chunkSize)) + 1;
const ind = this.getIndex(pos);
for (let x = -r; x <= r; x++) {
for (let y = -r; y <= r; y++) {
const chunk = this.valChunk(ind.x + x, ind.y + y);
if (chunk !== null)
result.push(...chunk.getNearbyActors(ignore, pos, radius));
}
}
return result;
}
//
// Chunks - Access
//
/**
* Check if a chunk exists.
* @param {number} x Index along x-axis
* @param {number} y Index along y-axis
* @returns {boolean} Whether chunk exists
*/
exists(x, y) {
if (!this.chunks.has(x))
return false;
return this.chunks.get(x).has(y);
}
/**
* Get the chunk indices at a specific position.
* @param {Vec2} pos Position to check at
* @returns {{x: number, y: number}} Chunk indices
*/
getIndex(pos) {
let xt = Math.floor(pos.x / (this.unitSize * this.chunkSize));
let yt = Math.floor(pos.y / (this.unitSize * this.chunkSize));
return {x: xt, y: yt};
}
/**
* Get the chunk at specified indices.
* @param {number} x Index along x-axis
* @param {number} y Index along y-axis
* @returns {Chunk|null} Chunk at indices, or null if it does not exist
*/
getChunk(x, y) {
if (!this.chunks.has(x))
return null;
if (!this.chunks.get(x).has(y))
return null;
return this.chunks.get(x).get(y);
}
/**
* Get a chunk at a specified position.
* @param {Vec2} pos Position to get chunk at
* @returns {Chunk|null} Chunk at position, or null if it does not exist
*/
getChunkAt(pos) {
let xt = Math.floor(pos.x / (this.unitSize * this.chunkSize));
let yt = Math.floor(pos.y / (this.unitSize * this.chunkSize));
if (!this.chunks.has(xt))
return null;
if (!this.chunks.get(xt).has(yt))
return null;
return this.chunks.get(xt).get(yt);
}
/**
* Get all chunks within an area. Will not include null chunks.
* @param {Vec2} pos Center of area
* @param {number} rx Width of area
* @param {number} ry Height of area
* @returns {Chunk[]} Chunks within area
*/
getNearby(pos, rx, ry) {
pos = this.getIndex(pos);
const chunks = [];
for (let xi = -rx; xi <= rx; xi++) {
for (let yi = -ry; yi <= ry; yi++) {
this.valChunk(pos.x + xi, pos.y + yi);
chunks.push(this.chunks.get(pos.x + xi).get(pos.y + yi));
}
}
return chunks;
}
/**
* Run a function for each chunk.
* @param {function} func Function to run
*/
forAll(func = chunk => {}) {
for (const x of this.chunks.keys())
for (const chunk of this.chunks.get(x).values())
func(chunk);
}
/**
* Get all chunks within specified bounds.
* @param {Bounds} bounds Bounding box
* @returns {Chunk[]} Chunks within bounds
*/
getInBounds(bounds) {
return this.getFromTo(bounds.min, bounds.max);
}
/**
* Get all chunks within two corner points.
* @param {Vec2} from Minimum corner
* @param {Vec2} to Maximum corner
* @returns {Chunk[]} Chunks within corners
*/
getFromTo(from, to) {
const min = this.getIndex(from);
const max = this.getIndex(to);
const chunks = [];
for (let x = min.x; x <= max.x + 1; x++) {
for (let y = min.y; y <= max.y + 1; y++) {
const chunk = this.genChunk(x, y);
if (chunk !== null)
chunks.push(chunk);
}
}
return chunks;
}
//
// Chunks - Generation & Validation
//
/**
* Generate a chunk at specified indices.
* @param {number} x Index along x-axis
* @param {number} y Index along y-axis
* @returns {Chunk} Chunk that was generated or already existed
*/
genChunk(x, y) {
if (!this.chunks.has(x))
this.chunks.set(x, new Map());
if (!this.chunks.get(x).has(y)) {
const chunk = new Chunk(this.space, x, y)
this.chunks.get(x).set(y, chunk);
this.space.chunkAdded(chunk);
this.count++;
return chunk;
}
return this.getChunk(x, y);
}
/**
* Get a chunk at specified indices, and create it if it does not yet exist.
* @param {number} x Index along x-axis
* @param {number} y Index along y-axis
* @returns {Chunk} Chunk that was generated or already existed
*/
valChunk(x, y) {
if (this.chunks.has(x))
if (this.chunks.get(x).has(y))
return this.chunks.get(x).get(y);
return this.genChunk(x, y);
}
/**
* Get a chunk at a specified position, and create it if it does not yet exist.
* @param {Vec2} pos Position to get chunk at
* @returns {Chunk} Chunk that was generated or already existed
*/
valChunkAt(pos) {
let x = Math.floor(pos.x / (this.unitSize * this.chunkSize));
let y = Math.floor(pos.y / (this.unitSize * this.chunkSize));
return this.valChunk(x, y);
}
/**
* Set a chunk at specific indices.
* @param {number} x Index along x-axisIndex along y-axis
* @param {number} y Index along x-axisIndex along y-axis
* @param {Chunk} chunk Chunk to set at indices
* @private
*/
_setChunk(x, y, chunk) {
if (!this.chunks.has(x))
this.chunks.set(x, new Map());
this.chunks.get(x).set(y, chunk);
}
/**
* Replace all chunks currently being stored. Used for routine cleanup.
* @param {Chunk[]} chunks Chunks to replace with
* @private
*/
_replaceAll(chunks) {
for (const x of this.chunks.keys())
for (const c of this.chunks.get(x).values())
if (c.persistent || c.actors.size !== 0)
chunks.push(c);
this.chunks = new Map();
this.count = chunks.length;
if (this.counter)
this.counter.innerText = this.count;
for (const c of chunks)
this._setChunk(c.x, c.y, c);
}
}
export default ChunkMgr;