Source: syngen/streamer.js

/**
 * Provides a helper for conserving resources by only streaming eligible props.
 * Systems can register and deregister props for streaming.
 * Props are dynamically instantiated and destroyed based on their eligibility.
 * Eligibility is determined by a configurable maximum distance from the observer, sorting, and limits.
 * @namespace
 */
syngen.streamer = (() => {
  const registry = new Map(),
    registryTree = syngen.utility.octree.create(),
    streamed = new Map()

  let currentVector = syngen.utility.vector3d.create(),
    limit = Infinity,
    radius = syngen.const.speedOfSound,
    shouldForce = false,
    sort = (a, b) => a.distance - b.distance

  function createRegisteredProp(token) {
    if (!registry.has(token)) {
      return
    }

    const {options, prototype} = registry.get(token)
    const prop = syngen.props.create(prototype, options)

    streamed.set(token, prop)
  }

  function destroyStreamedProp(token) {
    if (!streamed.has(token)) {
      return
    }

    const prop = streamed.get(token)

    syngen.props.destroy(prop)
    streamed.delete(token)
  }

  function generateToken() {
    let token

    do {
      token = syngen.utility.uuid()
    } while (registry.has(token))

    return token
  }

  function getStreamableProps() {
    const props = [
      // Select nearby registered props and coerce into instances of prototype
      // so it's easier to sort and limit them (e.g. check prototype or options)
      // (the instantiated flag should indicate whether spawned)
      ...registryTree.retrieve({
        depth: radius * 2,
        height: radius * 2,
        width: radius * 2,
        x: currentVector.x - radius,
        y: currentVector.y - radius,
        z: currentVector.z - radius,
      }).filter(({token}) => !streamed.has(token)).map((registeredProp) => Object.setPrototypeOf({
        ...registeredProp.options,
        distance: currentVector.distance(registeredProp),
      }, registeredProp.prototype)),

      // Currently streamed props
      ...streamed.values(),
    ].filter((prop) => prop.distance <= radius)

    if (!isFinite(limit)) {
      return props
    }

    return props.sort(sort).slice(0, limit)
  }

  return {
    /**
     * Deregisters the streamed prop with `token`.
     * Beware that it isn't destroyed.
     * @memberof syngen.streamer
     * @param {String} token
     */
    deregisterProp: function(token) {
      const registeredProp = registry.get(token)

      if (!registeredProp) {
        return this
      }

      registry.delete(token)
      registryTree.remove(registeredProp)

      return this
    },
    /**
     * Destroys the streamed prop with `token`.
     * Beware that it isn't deregistered.
     * @memberof syngen.streamer
     * @param {String} token
     */
    destroyStreamedProp: function (token) {
      destroyStreamedProp(token)
      return this
    },
    /**
     * Returns the streaming prop limit, if any.
     * @memberof syngen.streamer
     * @returns {Number|Infinity}
     */
    getLimit: () => limit,
    /**
     * Returns the streaming radius, if any.
     * @memberof syngen.streamer
     * @returns {Number|Infinity}
     */
    getRadius: () => radius,
    /**
     * Returns the definition for the prop registered with `token`, if one exists.
     * @memberof syngen.streamer
     * @param {String} token
     * @returns {Object|undefined}
     */
    getRegisteredProp: (token) => registry.get(token),
    /**
     * Returns the definitions for all registered props.
     * @memberof syngen.streamer
     * @returns {Object[]}
     */
    getRegisteredProps: () => [...registry.values()],
    /**
     * Returns the prop with `token`, if one is streaming.
     * @memberof syngen.streamer
     * @param {String} token
     * @returns {syngen.prop.base|undefined}
     */
    getStreamedProp: (token) => streamed.get(token),
    /**
     * Returns all streaming props.
     * @memberof syngen.streamer
     * @returns {engine.prop.base[]}
     */
    getStreamedProps: () => [...streamed.values()],
    /**
     * Returns whether a prop is registered for `token`.
     * @memberof syngen.streamer
     * @param {String} token
     * @returns {Boolean}
     */
    hasRegisteredProp: (token) => registry.has(token),
    /**
     * Returns whether a prop is streaming with `token`.
     * @memberof syngen.streamer
     * @param {String} token
     * @returns {Boolean}
     */
    hasStreamedProp: (token) => streamed.has(token),
    /**
     * Registers a prop with `prototype` and `options` and returns its token.
     * @memberof syngen.streamer
     * @param {engine.prop.base} prototype
     * @param {Object} [options]
     * @returns {String}
     */
    registerProp: function(prototype, options = {}) {
      const token = generateToken()

      const registeredProp = {
        options: {
          ...options,
          token,
        },
        prototype,
        token,
        x: options.x,
        y: options.y,
        z: options.z,
      }

      registry.set(token, registeredProp)
      registryTree.insert(registeredProp)

      shouldForce = true

      return token
    },
    /**
     * Clears and destroys all registered and streaming props.
     * @listens syngen.state#event:reset
     * @memberof syngen.streamer
     */
    reset: function() {
      registry.clear()
      registryTree.clear()

      streamed.forEach((streamedProp) => streamedProp.destroy())
      streamed.clear()

      shouldForce = false

      return this
    },
    /**
     * Sets the streaming prop limit.
     * @memberof syngen.streamer
     * @param {Number} value
     */
    setLimit: function (value) {
      if (value > 0) {
        limit = Number(value) || Infinity
        shouldForce = true
      }
      return this
    },
    /**
     * Sets the streaming radius.
     * @memberof syngen.streamer
     * @param {Number} value
     */
    setRadius: function (value) {
      radius = Number(value) || 0
      shouldForce = true
      return this
    },
    /**
     * Sets the sorting method.
     * @memberof syngen.streamer
     * @param {Function} value
     */
    setSort: function (value) {
      if (typeof sort == 'function') {
        sort = value
        shouldForce = true
      }
      return this
    },
    /**
     * Updates the streamed props with respect to the observer's coordinates.
     * @listens syngen.loop#event:frame
     * @memberof syngen.streamer
     * @param {Boolean} [force=false]
     */
    update: function (force = false) {
      const positionVector = syngen.position.getVector()

      if (!force && !shouldForce && currentVector.equals(positionVector)) {
        return this
      }

      currentVector = positionVector
      shouldForce = false

      const nowStreaming = new Set(),
        streamable = getStreamableProps()

      for (const {token} of streamable) {
        if (!streamed.has(token)) {
          createRegisteredProp(token)
        }
        nowStreaming.add(token)
      }

      for (const token of streamed.keys()) {
        if (!nowStreaming.has(token)) {
          destroyStreamedProp(token)
        }
      }

      return this
    },
    /**
     * Updates the `options` for the prop registered with `token`.
     * To change its prototype, its token must be deregistered.
     * @memberof syngen.streamer
     * @param {String} token
     * @param {Object} [options]
     */
    updateRegisteredProp: function (token, options = {}) {
      const registeredProp = propRegistry.get(token)

      if (!registeredProp) {
        return this
      }

      registeredProp.options = {
        ...registeredProp.options,
        ...options,
        token,
      }

      return this
    },
  }
})()

syngen.loop.on('frame', ({paused}) => {
  if (paused) {
    return
  }

  syngen.streamer.update()
})

syngen.state.on('reset', () => syngen.streamer.reset())