mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 04:05:49 +00:00
275 lines
7.3 KiB
JavaScript
275 lines
7.3 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
"use strict";
|
|
|
|
// Import as different name `coreEmit`, so we don't conflict
|
|
// with the global `window` listener itself.
|
|
const { emit: coreEmit } = require("sdk/event/core");
|
|
|
|
/**
|
|
* Representational wrapper around AudioNodeActors. Adding and destroying
|
|
* AudioNodes should be performed through the AudioNodes collection.
|
|
*
|
|
* Events:
|
|
* - `connect`: node, destinationNode, parameter
|
|
* - `disconnect`: node
|
|
*/
|
|
const AudioNodeModel = Class({
|
|
extends: EventTarget,
|
|
|
|
// Will be added via AudioNodes `add`
|
|
collection: null,
|
|
|
|
initialize: function (actor) {
|
|
this.actor = actor;
|
|
this.id = actor.actorID;
|
|
this.connections = [];
|
|
},
|
|
|
|
/**
|
|
* After instantiating the AudioNodeModel, calling `setup` caches values
|
|
* from the actor onto the model. In this case, only the type of audio node.
|
|
*
|
|
* @return promise
|
|
*/
|
|
setup: Task.async(function* () {
|
|
yield this.getType();
|
|
}),
|
|
|
|
/**
|
|
* A proxy for the underlying AudioNodeActor to fetch its type
|
|
* and subsequently assign the type to the instance.
|
|
*
|
|
* @return Promise->String
|
|
*/
|
|
getType: Task.async(function* () {
|
|
this.type = yield this.actor.getType();
|
|
return this.type;
|
|
}),
|
|
|
|
/**
|
|
* Stores connection data inside this instance of this audio node connecting
|
|
* to another node (destination). If connecting to another node's AudioParam,
|
|
* the second argument (param) must be populated with a string.
|
|
*
|
|
* Connecting nodes is idempotent. Upon new connection, emits "connect" event.
|
|
*
|
|
* @param AudioNodeModel destination
|
|
* @param String param
|
|
*/
|
|
connect: function (destination, param) {
|
|
let edge = findWhere(this.connections, { destination: destination.id, param: param });
|
|
|
|
if (!edge) {
|
|
this.connections.push({ source: this.id, destination: destination.id, param: param });
|
|
coreEmit(this, "connect", this, destination, param);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clears out all internal connection data. Emits "disconnect" event.
|
|
*/
|
|
disconnect: function () {
|
|
this.connections.length = 0;
|
|
coreEmit(this, "disconnect", this);
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that resolves to an array of objects containing
|
|
* both a `param` name property and a `value` property.
|
|
*
|
|
* @return Promise->Object
|
|
*/
|
|
getParams: function () {
|
|
return this.actor.getParams();
|
|
},
|
|
|
|
/**
|
|
* Takes a `dagreD3.Digraph` object and adds this node to
|
|
* the graph to be rendered.
|
|
*
|
|
* @param dagreD3.Digraph
|
|
*/
|
|
addToGraph: function (graph) {
|
|
graph.addNode(this.id, {
|
|
type: this.type,
|
|
label: this.type.replace(/Node$/, ""),
|
|
id: this.id
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Takes a `dagreD3.Digraph` object and adds edges to
|
|
* the graph to be rendered. Separate from `addToGraph`,
|
|
* as while we depend on D3/Dagre's constraints, we cannot
|
|
* add edges for nodes that have not yet been added to the graph.
|
|
*
|
|
* @param dagreD3.Digraph
|
|
*/
|
|
addEdgesToGraph: function (graph) {
|
|
for (let edge of this.connections) {
|
|
let options = {
|
|
source: this.id,
|
|
target: edge.destination
|
|
};
|
|
|
|
// Only add `label` if `param` specified, as this is an AudioParam
|
|
// connection then. `label` adds the magic to render with dagre-d3,
|
|
// and `param` is just more explicitly the param, ignoring
|
|
// implementation details.
|
|
if (edge.param) {
|
|
options.label = options.param = edge.param;
|
|
}
|
|
|
|
graph.addEdge(null, this.id, edge.destination, options);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Constructor for a Collection of `AudioNodeModel` models.
|
|
*
|
|
* Events:
|
|
* - `add`: node
|
|
* - `remove`: node
|
|
* - `connect`: node, destinationNode, parameter
|
|
* - `disconnect`: node
|
|
*/
|
|
const AudioNodesCollection = Class({
|
|
extends: EventTarget,
|
|
|
|
model: AudioNodeModel,
|
|
|
|
initialize: function () {
|
|
this.models = new Set();
|
|
this._onModelEvent = this._onModelEvent.bind(this);
|
|
},
|
|
|
|
/**
|
|
* Iterates over all models within the collection, calling `fn` with the
|
|
* model as the first argument.
|
|
*
|
|
* @param Function fn
|
|
*/
|
|
forEach: function (fn) {
|
|
this.models.forEach(fn);
|
|
},
|
|
|
|
/**
|
|
* Creates a new AudioNodeModel, passing through arguments into the AudioNodeModel
|
|
* constructor, and adds the model to the internal collection store of this
|
|
* instance.
|
|
*
|
|
* Also calls `setup` on the model itself, and sets up event piping, so that
|
|
* events emitted on each model propagate to the collection itself.
|
|
*
|
|
* Emits "add" event on instance when completed.
|
|
*
|
|
* @param Object obj
|
|
* @return Promise->AudioNodeModel
|
|
*/
|
|
add: Task.async(function* (obj) {
|
|
let node = new this.model(obj);
|
|
node.collection = this;
|
|
yield node.setup();
|
|
|
|
this.models.add(node);
|
|
|
|
node.on("*", this._onModelEvent);
|
|
coreEmit(this, "add", node);
|
|
return node;
|
|
}),
|
|
|
|
/**
|
|
* Removes an AudioNodeModel from the internal collection. Calls `delete` method
|
|
* on the model, and emits "remove" on this instance.
|
|
*
|
|
* @param AudioNodeModel node
|
|
*/
|
|
remove: function (node) {
|
|
this.models.delete(node);
|
|
coreEmit(this, "remove", node);
|
|
},
|
|
|
|
/**
|
|
* Empties out the internal collection of all AudioNodeModels.
|
|
*/
|
|
reset: function () {
|
|
this.models.clear();
|
|
},
|
|
|
|
/**
|
|
* Takes an `id` from an AudioNodeModel and returns the corresponding
|
|
* AudioNodeModel within the collection that matches that id. Returns `null`
|
|
* if not found.
|
|
*
|
|
* @param Number id
|
|
* @return AudioNodeModel|null
|
|
*/
|
|
get: function (id) {
|
|
return findWhere(this.models, { id: id });
|
|
},
|
|
|
|
/**
|
|
* Returns the count for how many models are a part of this collection.
|
|
*
|
|
* @return Number
|
|
*/
|
|
get length() {
|
|
return this.models.size;
|
|
},
|
|
|
|
/**
|
|
* Returns detailed information about the collection. used during tests
|
|
* to query state. Returns an object with information on node count,
|
|
* how many edges are within the data graph, as well as how many of those edges
|
|
* are for AudioParams.
|
|
*
|
|
* @return Object
|
|
*/
|
|
getInfo: function () {
|
|
let info = {
|
|
nodes: this.length,
|
|
edges: 0,
|
|
paramEdges: 0
|
|
};
|
|
|
|
this.models.forEach(node => {
|
|
let paramEdgeCount = node.connections.filter(edge => edge.param).length;
|
|
info.edges += node.connections.length - paramEdgeCount;
|
|
info.paramEdges += paramEdgeCount;
|
|
});
|
|
return info;
|
|
},
|
|
|
|
/**
|
|
* Adds all nodes within the collection to the passed in graph,
|
|
* as well as their corresponding edges.
|
|
*
|
|
* @param dagreD3.Digraph
|
|
*/
|
|
populateGraph: function (graph) {
|
|
this.models.forEach(node => node.addToGraph(graph));
|
|
this.models.forEach(node => node.addEdgesToGraph(graph));
|
|
},
|
|
|
|
/**
|
|
* Called when a stored model emits any event. Used to manage
|
|
* event propagation, or listening to model events to react, like
|
|
* removing a model from the collection when it's destroyed.
|
|
*/
|
|
_onModelEvent: function (eventName, node, ...args) {
|
|
if (eventName === "remove") {
|
|
// If a `remove` event from the model, remove it
|
|
// from the collection, and let the method handle the emitting on
|
|
// the collection
|
|
this.remove(node);
|
|
} else {
|
|
// Pipe the event to the collection
|
|
coreEmit(this, eventName, [node].concat(args));
|
|
}
|
|
}
|
|
});
|