HollowBond/Public/ecma/Managers/MapManager.ecma.js

285 lines
9.8 KiB
JavaScript

"use strict";
const MapManager = (function(){
const MapManager = function(hollow_bond){
const self = this,
frontier_selected_keys = new Set();
let maximum_chunks = 0,
maximum_complete_layer = 0,
frontier_layer = 0;
this.current_chunk = {x : 0, y : 0, z : 0};
// Colección de Chunks activos indexados por "x_y_z"
this.chunks = {};
this.frames = {};
this.rooms = {};
this.holes = {};
this.stairs = {};
this.elevators = {};
const get_chunk_key = (x, y, z) => `${x}_${y}_${z}`;
this.get_chunk = (x, y, z) => self.chunks[get_chunk_key(x, y, z)] || null;
this.clear = () => {
self.chunks = {};
self.frames = {};
self.rooms = {};
self.holes = {};
self.stairs = {};
self.elevators = {};
};
/**
* Destruye y libera un Chunk de la memoria activa.
*/
const unload_chunk = (x, y, z) => {
const key = get_chunk_key(x, y, z),
visualizer = (
hollow_bond.inspector.on ? hollow_bond.inspector :
hollow_bond.dungeon.on ? hollow_bond.dungeon :
null);
if(!self.chunks[key])
return;
// Avisamos a DungeonManager si está activo para que elimine los meshes 3D
visualizer && visualizer.remove_chunk(self.chunks[key]);
// Limpiamos referencias de frames y entidades de ese Chunk
for(const id in self.chunks[key].frames) delete self.frames[id];
for(const id in self.chunks[key].rooms) delete self.rooms[id];
for(const id in self.chunks[key].holes) delete self.holes[id];
for(const id in self.chunks[key].stairs) delete self.stairs[id];
for(const id in self.chunks[key].elevators) delete self.elevators[id];
delete self.chunks[key];
};
/**
* Actualiza el centro del streaming.
* Carga los nuevos vecinos que toquen y descarga los que queden lejos.
*/
this.update_active_center = (cx, cy, cz, callback = null) => {
const newly_created = [],
pending_targets = [
[0, 0, 0], // Centro actual
[-1, 0, 0], // Oeste
[1, 0, 0], // Este
[0, -1, 0], // Abajo
[0, 1, 0], // Arriba
[0, 0, -1], // Norte
[0, 0, 1] // Sur
].map(([ox, oy, oz]) => [cx + ox, cy + oy, cz + oz]).filter(([tx, ty, tz]) => (
!self.chunks[get_chunk_key(tx, ty, tz)] && chunk_exists(tx, ty, tz)
)),
process_uploads = () => {
for(const key in self.chunks)
Math.max(Math.abs(self.chunks[key].x - cx), Math.abs(self.chunks[key].y - cy), Math.abs(self.chunks[key].z - cz)) > 2 &&
unload_chunk(self.chunks[key].x, self.chunks[key].y, self.chunks[key].z);
};
self.current_chunk.x = cx;
self.current_chunk.y = cy;
self.current_chunk.z = cz;
if(callback){
let i = 0;
const process_next = () => {
if(i < pending_targets.length)
generate_chunk(...pending_targets[i ++], chunk => {
chunk && newly_created.push(chunk);
process_next();
});
else{
process_uploads();
callback(newly_created);
};
};
process_next();
return;
};
for(const [tx, ty, tz] of pending_targets){
const chunk = generate_chunk(tx, ty, tz);
chunk && newly_created.push(chunk);
};
process_uploads();
return newly_created;
};
/**
* Calcula el límite de capas completas y la selección exacta de la capa frontera.
*/
const setup_budget_layers = () => {
frontier_selected_keys.clear();
if(maximum_chunks <= 0)
return;
// 1. Caso base: 1 Chunk exacto (solo el origen)
if(maximum_chunks === 1){
maximum_complete_layer = 0;
frontier_layer = 0;
return;
};
// Buscamos si caben cubos completos mayores: (2*L + 1)^3
let layer = 0, leftover;
// Capa 0 está completa (el Chunk 0, 0, 0)
maximum_complete_layer = 0;
frontier_layer = 1;
// for(; Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks; layer ++);
while(Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks){
layer++;
}
maximum_complete_layer = layer; // Capa 100% llena
frontier_layer = layer + 1; // Capa frontera
leftover = maximum_chunks - Math.pow(2 * maximum_complete_layer + 1, 3);
if(leftover <= 0)
return;
// 2. Recolectamos todas las coordenadas de la cáscara cúbica frontier_layer
const candidates = [];
for(let cx = -frontier_layer; cx <= frontier_layer; cx ++)
for(let cy = -frontier_layer; cy <= frontier_layer; cy ++)
for(let cz = -frontier_layer; cz <= frontier_layer; cz ++)
Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz)) === frontier_layer && candidates.push({
key : `${cx}_${cy}_${cz}`,
manhattan : Math.abs(cx) + Math.abs(cy) + Math.abs(cz),
score : hollow_bond.hash.chunk_presence(cx, cy, cz)
});
// 3. Ordenamos: primero los que tienen contacto de cara (menor distancia Manhattan),
// y dentro de ellos por su score determinista del hash.
candidates.sort((a, b) => (
a.manhattan !== b.manhattan ? a.manhattan - b.manhattan :
a.score - b.score));
// 4. Seleccionamos exactamente 'leftover'
for(let i = 0, count = Math.min(leftover, candidates.length); i < count; i ++)
frontier_selected_keys.add(candidates[i].key);
};
const chunk_exists = (cx, cy, cz) => {
// Modo Infinito
if(maximum_chunks <= 0)
return true;
const layer = Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz));
return (
layer <= maximum_complete_layer ? true :
layer > frontier_layer ? false :
frontier_selected_keys.has(`${cx}_${cy}_${cz}`));
};
const generate_chunk = (x, y, z, callback = null) => {
// Si el Chunk no existe en el presupuesto, no se crea
if(!chunk_exists(x, y, z)){
callback && callback();
return null;
};
const key = get_chunk_key(x, y, z);
if(self.chunks[key]){
callback && callback();
return self.chunks[key];
};
const visualizer = (
hollow_bond.inspector.on ? hollow_bond.inspector :
hollow_bond.dungeon.on ? hollow_bond.dungeon :
null),
on_chunk_ready = () => {
Object.assign(self.frames, self.chunks[key].frames);
Object.assign(self.rooms, self.chunks[key].rooms);
Object.assign(self.holes, self.chunks[key].holes);
Object.assign(self.stairs, self.chunks[key].stairs);
Object.assign(self.elevators, self.chunks[key].elevators);
visualizer && visualizer.sync_chunk(self.chunks[key]);
callback && callback();
};
self.chunks[key] = new ChunkModel(hollow_bond, {
x : x,
y : y,
z : z,
neighbors : {
west : chunk_exists(x - 1, y, z),
east : chunk_exists(x + 1, y, z),
down : chunk_exists(x, y - 1, z),
up : chunk_exists(x, y + 1, z),
north : chunk_exists(x, y, z - 1),
south : chunk_exists(x, y, z + 1)
}
}, callback ? on_chunk_ready : null);
callback || on_chunk_ready();
return self.chunks[key];
};
this.generate = (seed = null, new_maximum_chunks = 0, callback = null) => {
self.clear();
if(seed !== null)
hollow_bond.set_seed(seed);
maximum_chunks = new_maximum_chunks;
setup_budget_layers();
if(callback)
return new Promise(resolve => {
self.update_active_center(0, 0, 0, () => {
console.log(self.chunks);
callback(self.chunks);
resolve(self.chunks);
});
});
self.update_active_center(0, 0, 0);
console.log(self.chunks);
return self.chunks;
};
this.generate_asynchronous = (seed = null, new_maximum_chunks = 0, callback = null) => self.generate(seed, new_maximum_chunks, callback || (() => {}));
const constructor = () => {};
constructor();
};
return MapManager;
})();