pixel/js/state-index.js
2026-06-02 17:19:36 +09:00

103 lines
3.8 KiB
JavaScript

(function () {
'use strict';
const root = window.PixelIslandModules ||= {};
function tileKey(x, y) {
return `${Math.round(x)},${Math.round(y)}`;
}
function pushBucket(map, key, item) {
const bucket = map.get(key);
if (bucket) bucket.push(item);
else map.set(key, [item]);
}
function build(state) {
const index = {
assetById: new Map(),
staticById: new Map(),
dynamicById: new Map(),
objectsById: new Map(),
placedByTile: new Map(),
dynamicByHomeTile: new Map(),
objectIdsByAssetId: new Map()
};
for (const asset of state.assets || []) {
if (asset?.id) index.assetById.set(asset.id, asset);
}
for (const placed of state.placed || []) {
if (!placed?.id) continue;
index.staticById.set(placed.id, placed);
index.objectsById.set(placed.id, { kind: 'static', object: placed });
pushBucket(index.placedByTile, tileKey(placed.x, placed.y), placed);
pushBucket(index.objectIdsByAssetId, placed.assetId, placed.id);
}
for (const summon of state.dynamicSummons || []) {
if (!summon?.id) continue;
index.dynamicById.set(summon.id, summon);
index.objectsById.set(summon.id, { kind: 'dynamic', object: summon });
pushBucket(index.dynamicByHomeTile, tileKey(summon.homeX, summon.homeY), summon);
pushBucket(index.objectIdsByAssetId, summon.assetId, summon.id);
}
return index;
}
function reduce(state, event, handlers = {}) {
if (!state || !event) return state;
switch (event.type) {
case 'asset.upsert': {
const asset = handlers.unpackAsset ? handlers.unpackAsset(event.asset) : event.asset;
if (!asset?.id) return state;
const list = state.assets ||= [];
const index = list.findIndex((item) => item.id === asset.id);
if (index >= 0) list[index] = asset;
else list.unshift(asset);
break;
}
case 'asset.delete': {
const assetId = event.assetId;
if (!assetId) return state;
const removed = new Set();
for (const item of state.placed || []) if (item.assetId === assetId) removed.add(item.id);
for (const item of state.dynamicSummons || []) if (item.assetId === assetId) removed.add(item.id);
state.assets = (state.assets || []).filter((asset) => asset.id !== assetId);
state.placed = (state.placed || []).filter((item) => item.assetId !== assetId);
state.dynamicSummons = (state.dynamicSummons || []).filter((item) => item.assetId !== assetId);
delete state.assetVotes?.[assetId];
delete state.hiddenAssets?.[assetId];
for (const id of removed) {
delete state.objectVotes?.[id];
delete state.hiddenObjects?.[id];
}
state.moderationReports = (state.moderationReports || []).filter((report) => !removed.has(report.objectId));
break;
}
case 'object.upsert': {
const object = handlers.unpackObject ? handlers.unpackObject(event.kind, event.object) : event.object;
if (!object?.id) return state;
const key = event.kind === 'dynamic' ? 'dynamicSummons' : 'placed';
const list = state[key] ||= [];
const index = list.findIndex((item) => item.id === object.id);
if (index >= 0) list[index] = object;
else list.push(object);
break;
}
case 'object.delete': {
const key = event.kind === 'dynamic' ? 'dynamicSummons' : 'placed';
state[key] = (state[key] || []).filter((item) => item.id !== event.objectId);
delete state.objectVotes?.[event.objectId];
delete state.hiddenObjects?.[event.objectId];
state.moderationReports = (state.moderationReports || []).filter((report) => report.objectId !== event.objectId);
break;
}
}
return state;
}
root.StateIndex = { build, reduce, tileKey };
})();