message: move WatchManager over to an ES6 Map
diff: @@ -18,11 +18,7 @@ import { SettingLevel } from "./SettingLevel";
  export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void;
  -const IRRELEVANT_ROOM: string = null;
  -
  -interface RoomWatcherMap {
  - [roomId: string]: CallbackFn[];
  -}
  +const IRRELEVANT_ROOM = Symbol("irrelevant-room");
  /**
  * Generalized management class for dealing with watchers on a per-handler (per-level)
  @@ -30,25 +26,25 @@ interface RoomWatcherMap {
  * class, which are then proxied outwards to any applicable watchers.
  */
  export class WatchManager {
  - private watchers: {[settingName: string]: RoomWatcherMap} = {};
  + private watchers = new Map<string, Map<string | symbol, CallbackFn[]>>(); // settingName -> roomId -> CallbackFn[]
  // Proxy for handlers to delegate changes to this manager
  public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) {
  - if (!this.watchers[settingName]) this.watchers[settingName] = {};
  - if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = [];
  - this.watchers[settingName][roomId].push(cb);
  + if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map());
  + if (!this.watchers.get(settingName).has(roomId)) this.watchers.get(settingName).set(roomId, []);
  + this.watchers.get(settingName).get(roomId).push(cb);
  }
  // Proxy for handlers to delegate changes to this manager
  public unwatchSetting(cb: CallbackFn) {
  - for (const settingName of Object.keys(this.watchers)) {
  - for (const roomId of Object.keys(this.watchers[settingName])) {
  + this.watchers.forEach((map) => {
  + map.forEach((callbacks) => {
  let idx;
  - while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) {
  - this.watchers[settingName][roomId].splice(idx, 1);
  - }
  - }
  + while ((idx = callbacks.indexOf(cb)) !== -1) {
  + callbacks.splice(idx, 1);
  }
  + });
  + });
  }
  public notifyUpdate(settingName: string, inRoomId: string | null, atLevel: SettingLevel, newValueAtLevel: any) {
  @@ -56,21 +52,21 @@ export class WatchManager {
  // we also don't have a reliable way to get the old value of a setting. Instead, we'll just
  // let it fall through regardless and let the receiver dedupe if they want to.
  - if (!this.watchers[settingName]) return;
  + if (!this.watchers.has(settingName)) return;
  - const roomWatchers = this.watchers[settingName];
  + const roomWatchers = this.watchers.get(settingName);
  const callbacks = [];
  - if (inRoomId !== null && roomWatchers[inRoomId]) {
  - callbacks.push(...roomWatchers[inRoomId]);
  + if (inRoomId !== null && roomWatchers.has(inRoomId)) {
  + callbacks.push(...roomWatchers.get(inRoomId));
  }
  if (!inRoomId) {
  - // Fire updates to all the individual room watchers too, as they probably
  - // care about the change higher up.
  - callbacks.push(...Object.values(roomWatchers).flat(1));
  - } else if (roomWatchers[IRRELEVANT_ROOM]) {
  - callbacks.push(...roomWatchers[IRRELEVANT_ROOM]);
  + // Fire updates to all the individual room watchers too, as they probably care about the change higher up.
  + const callbacks = Array.from(roomWatchers.values()).flat(1);
  + callbacks.push(...callbacks);
  + } else if (roomWatchers.has(IRRELEVANT_ROOM)) {
  + callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM));
  }
  for (const callback of callbacks) {
  