import React from 'react'; import Promise from 'bluebird'; import { extension } from '/common/util'; import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData } from '/core/datas'; import { Array1DRenderer, Array2DRenderer, ChartRenderer, GraphRenderer, LogRenderer, Renderer } from '/core/renderers'; import { TracerApi } from '/apis'; import { CompileError } from '/common/error'; class TracerManager { constructor() { this.interval = 500; this.paused = false; this.started = false; this.lineIndicator = null; this.file = { name: '', content: '', contributors: [] }; this.reset(); } setOnChangeRenderers(onChangeRenderers) { this.onChangeRenderers = onChangeRenderers; if (this.onChangeRenderers) this.onChangeRenderers(this.renderers); } setOnUpdateStatus(onUpdateStatus) { this.onUpdateStatus = onUpdateStatus; if (this.onUpdateStatus) { const { interval, paused, started } = this; this.onUpdateStatus({ interval, paused, started }); } } setOnUpdateLineIndicator(onUpdateLineIndicator) { this.onUpdateLineIndicator = onUpdateLineIndicator; if (this.onUpdateLineIndicator) this.onUpdateLineIndicator(this.lineIndicator); } setOnError(onError) { this.onError = onError; } render() { Object.values(this.datas).forEach(data => data.render()); } setInterval(interval) { this.interval = interval; if (this.onUpdateStatus) this.onUpdateStatus({ interval }); } setPaused(paused) { this.paused = paused; if (this.onUpdateStatus) this.onUpdateStatus({ paused }); } setStarted(started) { this.started = started; if (this.onUpdateStatus) this.onUpdateStatus({ started }); } setLineIndicator(lineIndicator) { this.lineIndicator = lineIndicator; if (this.onUpdateLineIndicator) this.onUpdateLineIndicator(lineIndicator); } setFile(file, initialRun) { this.file = file; if (initialRun) this.runInitial(); } reset(traces = []) { this.traces = traces; this.resetCursor(); this.stopTimer(); this.setPaused(false); this.setStarted(false); this.setLineIndicator(null); } resetCursor() { this.renderers = []; this.datas = {}; this.cursor = 0; this.chunkCursor = 0; if (this.onChangeRenderers) this.onChangeRenderers(this.renderers); } addTracer(className, tracerKey, title) { const [DataClass, RendererClass] = { Tracer: [Data, Renderer], LogTracer: [LogData, LogRenderer], Array2DTracer: [Array2DData, Array2DRenderer], Array1DTracer: [Array1DData, Array1DRenderer], ChartTracer: [ChartData, ChartRenderer], GraphTracer: [GraphData, GraphRenderer], }[className]; const data = new DataClass(); this.datas[tracerKey] = data; const renderer = ( ); this.renderers.push(renderer); if (this.onChangeRenderers) this.onChangeRenderers(this.renderers); } applyTrace() { if (this.cursor >= this.traces.length) return false; const trace = this.traces[this.cursor++]; const { tracerKey, method, args } = trace; try { if (method === 'construct') { const [className, title] = args; this.addTracer(className, tracerKey, title); return true; } else { const data = this.datas[tracerKey]; const delay = method === 'delay'; const newArgs = [...args]; if (delay) { const lineNumber = newArgs.shift(); this.setLineIndicator({ lineNumber, cursor: this.cursor }); } data[method](...newArgs); return !delay; } } catch (error) { if (this.started) this.handleError(error); return false; } } applyTraceChunk(render = true) { if (this.cursor >= this.traces.length) return false; while (this.applyTrace()) ; this.chunkCursor++; if (render) this.render(); return true; } startTimer() { this.stopTimer(); if (this.applyTraceChunk()) { this.timer = window.setTimeout(() => this.startTimer(), this.interval); } else { this.setPaused(false); this.setStarted(false); } } stopTimer() { if (this.timer) { window.clearInterval(this.timer); this.timer = null; } } execute() { const { name, content } = this.file; const ext = extension(name); if (ext in TracerApi) { return TracerApi[ext]({ code: content }); } else { return Promise.reject(new CompileError('Language Not Supported')); } } runInitial() { this.reset(); this.render(); this.execute() .then(traces => { this.reset(traces); this.applyTraceChunk(); }) .catch(error => { }); } run() { this.reset(); this.render(); this.execute() .then(traces => { this.reset(traces); this.resume(); this.setStarted(true); }) .catch(error => { this.handleError(error); }); } prev() { this.pause(); const lastChunk = this.chunkCursor - 1; this.resetCursor(); do { this.applyTraceChunk(false); } while (this.chunkCursor < lastChunk); this.render(); } resume() { this.startTimer(); this.setPaused(false); } pause() { this.stopTimer(); this.setPaused(true); } next() { this.pause(); this.applyTraceChunk(); } handleError(error) { console.error(error); if (this.onError) this.onError(error); } } const tracerManager = new TracerManager(); export default tracerManager;