summaryrefslogtreecommitdiff
path: root/src/scripts/controllers/simulationcontroller.ts
diff options
context:
space:
mode:
authorGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-01-24 12:06:09 +0100
committerGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-01-24 12:06:09 +0100
commitc96e6ffafb62bde1e08987b1fdf3c0786487f6ec (patch)
tree37eaf4cf199ca77dc131b4212c526b707adf2e30 /src/scripts/controllers/simulationcontroller.ts
Initial commit
Diffstat (limited to 'src/scripts/controllers/simulationcontroller.ts')
-rw-r--r--src/scripts/controllers/simulationcontroller.ts586
1 files changed, 586 insertions, 0 deletions
diff --git a/src/scripts/controllers/simulationcontroller.ts b/src/scripts/controllers/simulationcontroller.ts
new file mode 100644
index 00000000..8d9553e9
--- /dev/null
+++ b/src/scripts/controllers/simulationcontroller.ts
@@ -0,0 +1,586 @@
+///<reference path="../../../typings/index.d.ts" />
+///<reference path="mapcontroller.ts" />
+import * as $ from "jquery";
+import {MapView} from "../views/mapview";
+import {MapController, InteractionLevel, AppMode} from "./mapcontroller";
+import {Util} from "../util";
+import {StateCache} from "./simulation/statecache";
+import {ChartController} from "./simulation/chart";
+import {TaskViewController} from "./simulation/taskview";
+import {TimelineController} from "./simulation/timeline";
+
+
+export enum ColorRepresentation {
+ LOAD,
+ TEMPERATURE,
+ MEMORY
+}
+
+
+export class SimulationController {
+ public mapView: MapView;
+ public mapController: MapController;
+
+ public playing: boolean;
+ public currentTick: number;
+ public stateCache: StateCache;
+ public lastSimulatedTick: number;
+ public simulation: ISimulation;
+ public experiments: IExperiment[];
+ public currentExperiment: IExperiment;
+ public currentPath: IPath;
+ public sections: ISection[];
+ public currentSection: ISection;
+ public experimentSelectionMode: boolean;
+ public traces: ITrace[];
+ public schedulers: IScheduler[];
+ public sectionIndex: number;
+ public chartController: ChartController;
+ public timelineController: TimelineController;
+
+ public colorRepresentation: ColorRepresentation;
+ public rackToRoomMap: {[key: number]: number;};
+
+ private taskViewController: TaskViewController;
+ private tickerId: number;
+
+
+ public static showOrHideSimComponents(visibility: boolean): void {
+ if (visibility) {
+ $("#statistics-menu").removeClass("hidden");
+ $("#experiment-menu").removeClass("hidden");
+ $("#tasks-menu").removeClass("hidden");
+ $(".timeline-container").removeClass("hidden");
+ } else {
+ $("#statistics-menu").addClass("hidden");
+ $("#experiment-menu").addClass("hidden");
+ $("#tasks-menu").addClass("hidden");
+ $(".timeline-container").addClass("hidden");
+ }
+ }
+
+ constructor(mapController: MapController) {
+ this.mapController = mapController;
+ this.mapView = this.mapController.mapView;
+ this.simulation = this.mapController.mapView.simulation;
+ this.experiments = this.simulation.experiments;
+ this.taskViewController = new TaskViewController(this);
+ this.timelineController = new TimelineController(this);
+ this.chartController = new ChartController(this);
+
+ this.timelineController.setupListeners();
+ this.experimentSelectionMode = true;
+ this.sectionIndex = 0;
+
+ this.currentTick = 0;
+ this.playing = false;
+ this.stateCache = new StateCache(this);
+ this.colorRepresentation = ColorRepresentation.LOAD;
+
+ this.traces = [];
+ this.schedulers = [];
+
+ this.mapController.api.getAllTraces().then((data) => {
+ this.traces = data;
+ });
+
+ this.mapController.api.getAllSchedulers().then((data) => {
+ this.schedulers = data;
+ });
+ }
+
+ public enterMode() {
+ this.experimentSelectionMode = true;
+
+ if (this.mapController.interactionLevel === InteractionLevel.BUILDING) {
+ this.mapView.roomLayer.coloringMode = true;
+ this.mapView.dcObjectLayer.coloringMode = false;
+ } else if (this.mapController.interactionLevel === InteractionLevel.ROOM ||
+ this.mapController.interactionLevel === InteractionLevel.OBJECT) {
+ this.mapView.roomLayer.coloringMode = false;
+ this.mapView.dcObjectLayer.coloringMode = true;
+ } else if (this.mapController.interactionLevel === InteractionLevel.NODE) {
+ this.mapController.nodeModeController.goToObjectMode();
+ }
+
+ this.mapController.appMode = AppMode.SIMULATION;
+ this.mapView.dcObjectLayer.detailedMode = false;
+ this.mapView.gridLayer.setVisibility(false);
+ this.mapView.updateScene = true;
+
+ this.mapController.setAllMenuModes();
+ SimulationController.showOrHideSimComponents(true);
+ $(".mode-switch").attr("data-selected", "simulation");
+ $("#save-version-btn").hide();
+ $(".color-indicator").removeClass("hidden");
+
+ $("#change-experiment-btn").click(() => {
+ this.playing = false;
+ this.stateCache.stopCaching();
+ this.timelineController.update();
+ this.showExperimentsDialog();
+ });
+
+ this.setupColorMenu();
+ this.showExperimentsDialog();
+ }
+
+ private launchSimulation(): void {
+ this.onSimulationSectionChange();
+
+ this.chartController.setup();
+
+ this.stateCache.startCaching();
+
+ this.tickerId = setInterval(() => {
+ this.simulationTick();
+ }, 1000);
+ }
+
+ private onSimulationSectionChange(): void {
+ this.currentSection = this.currentPath.sections[this.sectionIndex];
+ this.mapView.currentDatacenter = this.currentSection.datacenter;
+
+ // Generate a map of all rack IDs in relation to their room IDs for use in room stats
+ this.rackToRoomMap = {};
+ this.currentSection.datacenter.rooms.forEach((room: IRoom) => {
+ room.tiles.forEach((tile: ITile) => {
+ if (tile.object !== undefined && tile.objectType === "RACK") {
+ this.rackToRoomMap[tile.objectId] = room.id;
+ }
+ });
+ });
+
+ if (this.mapController.interactionLevel === InteractionLevel.NODE) {
+ this.mapController.nodeModeController.goToObjectMode();
+ }
+ if (this.mapController.interactionLevel === InteractionLevel.OBJECT) {
+ this.mapController.objectModeController.goToRoomMode();
+ }
+ if (this.mapController.interactionLevel === InteractionLevel.ROOM) {
+ this.mapController.roomModeController.goToBuildingMode();
+ }
+
+ this.mapView.redrawMap();
+
+ this.mapView.zoomOutOnDC();
+ }
+
+ public exitMode() {
+ this.closeExperimentsDialog();
+
+ this.mapController.appMode = AppMode.CONSTRUCTION;
+ this.mapView.dcObjectLayer.detailedMode = true;
+ this.mapView.gridLayer.setVisibility(true);
+ this.mapView.redrawMap();
+
+ this.stateCache.stopCaching();
+ this.playing = false;
+
+ this.mapController.setAllMenuModes();
+ SimulationController.showOrHideSimComponents(false);
+
+ this.setColors();
+ $(".color-indicator").addClass("hidden")["popover"]("hide").off();
+ $(".mode-switch").attr("data-selected", "construction");
+ $("#save-version-btn").show();
+
+ clearInterval(this.tickerId);
+ }
+
+ public update() {
+ if (this.stateCache.cacheBlock) {
+ return;
+ }
+
+ this.setColors();
+ this.updateBuildingStats();
+ this.updateRoomStats();
+ this.chartController.update();
+ this.taskViewController.update();
+ }
+
+ public simulationTick(): void {
+ this.timelineController.update();
+
+ if (this.currentTick > this.lastSimulatedTick) {
+ this.currentTick = this.lastSimulatedTick;
+ this.playing = false;
+ this.timelineController.setButtonIcon();
+ }
+
+ if (this.playing) {
+ this.checkCurrentSimulationSection();
+ this.update();
+
+ if (!this.stateCache.cacheBlock) {
+ this.currentTick++;
+ }
+ }
+ }
+
+ public checkCurrentSimulationSection(): void {
+ for (let i = this.sections.length - 1; i >= 0; i--) {
+ if (this.currentTick >= this.sections[i].startTick) {
+ if (this.sectionIndex !== i) {
+ this.sectionIndex = i;
+ this.onSimulationSectionChange();
+ }
+ break;
+ }
+ }
+ }
+
+ public transitionFromBuildingToRoom(): void {
+ this.mapView.roomLayer.coloringMode = false;
+ this.mapView.dcObjectLayer.coloringMode = true;
+
+ this.setColors();
+ this.updateRoomStats();
+ this.chartController.update();
+ }
+
+ public transitionFromRoomToBuilding(): void {
+ this.mapView.roomLayer.coloringMode = true;
+ this.mapView.dcObjectLayer.coloringMode = false;
+
+ this.setColors();
+ this.updateBuildingStats();
+ this.chartController.update();
+ }
+
+ public transitionFromRoomToRack(): void {
+ this.setColors();
+ $("#statistics-menu").addClass("hidden");
+ this.chartController.update();
+ }
+
+ public transitionFromRackToRoom(): void {
+ this.setColors();
+ $("#statistics-menu").removeClass("hidden");
+ }
+
+ public transitionFromRackToNode(): void {
+ this.chartController.update();
+ }
+
+ public transitionFromNodeToRack(): void {
+ }
+
+ private showExperimentsDialog(): void {
+ $(".experiment-name-alert").hide();
+
+ this.populateExperimentsList();
+ this.populateDropdowns();
+
+ $(".experiment-row").click((event: JQueryEventObject) => {
+ if ($(event.target).hasClass("remove-experiment")) {
+ return;
+ }
+
+ let row = $(event.target).closest(".experiment-row");
+ this.prepareAndLaunchExperiment(this.experiments[row.index()]);
+ });
+
+ $(".experiment-list .list-body").on("click", ".remove-experiment", (event: JQueryEventObject) => {
+ event.stopPropagation();
+ let affectedRow = $(event.target).closest(".experiment-row");
+ let index = affectedRow.index();
+ let affectedExperiment = this.experiments[index];
+
+ MapController.showConfirmDeleteDialog("experiment", () => {
+ this.mapController.api.deleteExperiment(affectedExperiment.simulationId, affectedExperiment.id)
+ .then(() => {
+ this.experiments.splice(index, 1);
+ this.populateExperimentsList();
+ });
+ });
+ });
+
+ $("#new-experiment-btn").click(() => {
+ let nameInput = $("#new-experiment-name-input");
+ if (nameInput.val() === "") {
+ $(".experiment-name-alert").show();
+ return;
+ } else {
+ $(".experiment-name-alert").hide();
+ }
+
+ let newExperiment: IExperiment = {
+ id: -1,
+ name: nameInput.val(),
+ pathId: parseInt($("#new-experiment-path-select").val()),
+ schedulerName: $("#new-experiment-scheduler-select").val(),
+ traceId: parseInt($("#new-experiment-trace-select").val()),
+ simulationId: this.simulation.id
+ };
+
+ this.mapController.api.addExperimentToSimulation(this.simulation.id, newExperiment)
+ .then((data: IExperiment) => {
+ this.simulation.experiments.push(data);
+ this.prepareAndLaunchExperiment(data);
+ });
+ });
+
+ $(".window-close").click(() => {
+ this.exitMode();
+ });
+
+ $(".window-overlay").fadeIn(200);
+ }
+
+ private prepareAndLaunchExperiment(experiment: IExperiment): void {
+ this.prepareSimulationData(experiment);
+ this.launchSimulation();
+ this.closeExperimentsDialog();
+ }
+
+ private prepareSimulationData(experiment: IExperiment): void {
+ this.currentExperiment = experiment;
+ this.currentPath = this.getPathById(this.currentExperiment.pathId);
+ this.sections = this.currentPath.sections;
+ this.sectionIndex = 0;
+ this.currentTick = 0;
+ this.playing = false;
+ this.stateCache = new StateCache(this);
+ this.colorRepresentation = ColorRepresentation.LOAD;
+
+ this.sections.sort((a: ISection, b: ISection) => {
+ return a.startTick - b.startTick;
+ });
+
+ $("#experiment-menu-name").text(experiment.name);
+ $("#experiment-menu-path").text(SimulationController.getPathName(this.currentPath));
+ $("#experiment-menu-scheduler").text(experiment.schedulerName);
+ $("#experiment-menu-trace").text(experiment.trace.name);
+ }
+
+ private closeExperimentsDialog(): void {
+ $(".window-overlay").fadeOut(200);
+ $(".window-overlay input").val("");
+ }
+
+ private populateDropdowns(): void {
+ let pathDropdown = $("#new-experiment-path-select");
+ let traceDropdown = $("#new-experiment-trace-select");
+ let schedulerDropdown = $("#new-experiment-scheduler-select");
+
+ pathDropdown.empty();
+ for (let i = 0; i < this.simulation.paths.length; i++) {
+ pathDropdown.append(
+ $("<option>").text(SimulationController.getPathName(this.simulation.paths[i]))
+ .val(this.simulation.paths[i].id)
+ );
+ }
+
+ traceDropdown.empty();
+ for (let i = 0; i < this.traces.length; i++) {
+ traceDropdown.append(
+ $("<option>").text(this.traces[i].name)
+ .val(this.traces[i].id)
+ );
+ }
+
+ schedulerDropdown.empty();
+ for (let i = 0; i < this.schedulers.length; i++) {
+ schedulerDropdown.append(
+ $("<option>").text(this.schedulers[i].name)
+ .val(this.schedulers[i].name)
+ );
+ }
+ }
+
+ /**
+ * Populates the list of experiments.
+ */
+ private populateExperimentsList(): void {
+ let table = $(".experiment-list .list-body");
+ table.empty();
+
+ console.log("EXPERIMENT", this.experiments);
+ console.log("SIMULATION", this.simulation);
+
+ if (this.experiments.length === 0) {
+ $(".experiment-list").hide();
+ $(".no-experiments-alert").show();
+ } else {
+ $(".no-experiments-alert").hide();
+ this.experiments.forEach((experiment: IExperiment) => {
+ table.append(
+ '<div class="experiment-row">' +
+ ' <div>' + experiment.name + '</div>' +
+ ' <div>' + this.getPathNameById(experiment.pathId) + '</div>' +
+ ' <div>' + experiment.trace.name + '</div>' +
+ ' <div>' + experiment.schedulerName + '</div>' +
+ ' <div class="remove-experiment glyphicon glyphicon-remove"></div>' +
+ '</div>'
+ );
+ });
+ }
+ }
+
+ private getPathNameById(id: number): string {
+ for (let i = 0; i < this.simulation.paths.length; i++) {
+ if (id === this.simulation.paths[i].id) {
+ return SimulationController.getPathName(this.simulation.paths[i]);
+ }
+ }
+ }
+
+ private getPathById(id: number): IPath {
+ for (let i = 0; i < this.simulation.paths.length; i++) {
+ if (id === this.simulation.paths[i].id) {
+ return this.simulation.paths[i];
+ }
+ }
+ }
+
+ private static getPathName(path: IPath): string {
+ if (path.name === null) {
+ return "Path " + path.id;
+ } else {
+ return path.name;
+ }
+ }
+
+ private setColors() {
+ if (this.mapController.appMode === AppMode.SIMULATION) {
+ if (this.mapController.interactionLevel === InteractionLevel.BUILDING) {
+ this.mapView.roomLayer.intensityLevels = {};
+
+ this.stateCache.stateList[this.currentTick].roomStates.forEach((roomState: IRoomState) => {
+ if (this.colorRepresentation === ColorRepresentation.LOAD) {
+ this.mapView.roomLayer.intensityLevels[roomState.roomId] =
+ Util.determineLoadIntensityLevel(roomState.loadFraction);
+ }
+ });
+
+ this.mapView.roomLayer.draw();
+ this.mapView.dcObjectLayer.draw();
+ } else if (this.mapController.interactionLevel === InteractionLevel.ROOM ||
+ this.mapController.interactionLevel === InteractionLevel.OBJECT) {
+ this.mapView.dcObjectLayer.intensityLevels = {};
+
+ this.stateCache.stateList[this.currentTick].rackStates.forEach((rackState: IRackState) => {
+ if (this.colorRepresentation === ColorRepresentation.LOAD) {
+ this.mapView.dcObjectLayer.intensityLevels[rackState.rackId] =
+ Util.determineLoadIntensityLevel(rackState.loadFraction);
+ }
+ });
+
+ this.mapView.roomLayer.draw();
+ this.mapView.dcObjectLayer.draw();
+ }
+
+ if (this.mapController.interactionLevel === InteractionLevel.OBJECT ||
+ this.mapController.interactionLevel === InteractionLevel.NODE) {
+ this.stateCache.stateList[this.currentTick].machineStates.forEach((machineState: IMachineState) => {
+ let element = $('.node-element[data-id="' + machineState.machineId + '"] .node-element-content');
+ element.css("background-color", Util.convertIntensityToColor(
+ Util.determineLoadIntensityLevel(machineState.loadFraction)
+ ));
+
+ // Color all transparent icon overlays, as well
+ element = $('.node-element[data-id="' + machineState.machineId + '"] .icon-overlay');
+ element.css("background-color", Util.convertIntensityToColor(
+ Util.determineLoadIntensityLevel(machineState.loadFraction)
+ ));
+ });
+ }
+ } else {
+ this.mapView.roomLayer.coloringMode = false;
+ this.mapView.dcObjectLayer.coloringMode = false;
+
+ this.mapView.roomLayer.draw();
+ this.mapView.dcObjectLayer.draw();
+ }
+ }
+
+ /**
+ * Populates the building simulation menu with dynamic statistics concerning the state of all rooms in the building.
+ */
+ private updateBuildingStats(): void {
+ if (this.mapController.interactionLevel !== InteractionLevel.BUILDING) {
+ return;
+ }
+
+ console.log(this.stateCache);
+
+ let html;
+ let container = $(".building-stats-list");
+
+ container.children().remove("div");
+
+ this.stateCache.stateList[this.currentTick].roomStates.forEach((roomState: IRoomState) => {
+ if (this.colorRepresentation === ColorRepresentation.LOAD && roomState.room !== undefined) {
+ html = '<div>' +
+ ' <h4>' + roomState.room.name + '</h4>' +
+ ' <p>Load: ' + Math.round(roomState.loadFraction * 100) + '%</p>' +
+ '</div>';
+ container.append(html);
+ }
+ });
+
+ }
+
+ /**
+ * Populates the room simulation menu with dynamic statistics concerning the state of all racks in the room.
+ */
+ private updateRoomStats(): void {
+ if (this.mapController.interactionLevel !== InteractionLevel.ROOM) {
+ return;
+ }
+
+ $("#room-name-field").text(this.mapController.roomModeController.currentRoom.name);
+ $("#room-type-field").text(this.mapController.roomModeController.currentRoom.roomType);
+
+ let html;
+ let container = $(".room-stats-list");
+
+ container.children().remove("div");
+
+ this.stateCache.stateList[this.currentTick].rackStates.forEach((rackState: IRackState) => {
+ if (this.rackToRoomMap[rackState.rackId] !== this.mapController.roomModeController.currentRoom.id) {
+ return;
+ }
+ if (this.colorRepresentation === ColorRepresentation.LOAD) {
+ html = '<div>' +
+ ' <h4>' + rackState.rack.name + '</h4>' +
+ ' <p>Load: ' + Math.round(rackState.loadFraction * 100) + '%</p>' +
+ '</div>';
+ container.append(html);
+ }
+ });
+ }
+
+ private setupColorMenu(): void {
+ let html =
+ '<select class="form-control" title="Color Representation" id="color-representation-select">' +
+ ' <option value="1" selected>Load</option>' +
+ ' <option value="2">Power use</option>' +
+ '</select>';
+
+ let indicator = $(".color-indicator");
+ indicator["popover"]({
+ animation: true,
+ content: html,
+ html: true,
+ placement: "top",
+ title: "Colors represent:",
+ trigger: "manual"
+ });
+ indicator.click(() => {
+ //noinspection JSJQueryEfficiency // suppressed for dynamic element insertion
+ if ($("#color-representation-select").length) {
+ indicator["popover"]("hide");
+ } else {
+ indicator["popover"]("show");
+
+ let selectElement = $("#color-representation-select");
+ selectElement.change(() => {
+ console.log(selectElement.val());
+ });
+ }
+ });
+ }
+}