class GroupManager { constructor() { this.groups = new Map(); // groupId -> Set this.pieceToGroup = new Map(); // pieceId -> groupId this._nextId = 1; } createSingletonGroup(pieceId) { const gid = this._nextId++; this.groups.set(gid, new Set([pieceId])); this.pieceToGroup.set(pieceId, gid); return gid; } getGroupId(pieceId) { return this.pieceToGroup.get(pieceId) ?? null; } /** Returns the Set for the group containing pieceId, or null. */ getGroup(pieceId) { const gid = this.getGroupId(pieceId); if (gid === null) return null; return this.groups.get(gid) ?? null; } /** Returns all piece IDs in the same group as pieceId (including itself). */ getPeersOf(pieceId) { return this.getGroup(pieceId) ?? new Set([pieceId]); } /** * Merge the groups containing pieceIdA and pieceIdB into a single new group. * @returns {number} the new group ID */ merge(pieceIdA, pieceIdB) { const gidA = this.getGroupId(pieceIdA); const gidB = this.getGroupId(pieceIdB); if (gidA === null || gidB === null) return gidA ?? gidB; if (gidA === gidB) return gidA; // already in the same group const newGid = this._nextId++; const members = new Set([...this.groups.get(gidA), ...this.groups.get(gidB)]); this.groups.delete(gidA); this.groups.delete(gidB); this.groups.set(newGid, members); members.forEach(id => this.pieceToGroup.set(id, newGid)); return newGid; } /** Total number of distinct locked groups. */ get groupCount() { return this.groups.size; } /** Total number of tracked pieces. */ get pieceCount() { return this.pieceToGroup.size; } /** * Serialize to a plain array of arrays for localStorage. * @returns {number[][]} */ serialize() { return Array.from(this.groups.values()).map(set => Array.from(set)); } /** * Replace all internal state from a serialized groups array. * Used for applying remote state updates. * @param {number[][]} groupsList */ rebuildFromGroups(groupsList) { this.groups.clear(); this.pieceToGroup.clear(); this._nextId = 1; groupsList.forEach(members => { const gid = this._nextId++; const set = new Set(members); this.groups.set(gid, set); members.forEach(id => this.pieceToGroup.set(id, gid)); }); } /** * Rebuild from serialized data. * @param {number[][]} groupsList */ static deserialize(groupsList) { const gm = new GroupManager(); groupsList.forEach(members => { const gid = gm._nextId++; const set = new Set(members); gm.groups.set(gid, set); members.forEach(id => gm.pieceToGroup.set(id, gid)); }); return gm; } }