diff --git a/sim/dalboard.ts b/sim/dalboard.ts index a407a385..64db2f0b 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -17,6 +17,9 @@ namespace pxsim { radioState: RadioState; neopixelState: NeoPixelState; + // updates + updateSubscribers: (() => void)[]; + constructor() { super() this.id = "b" + Math_.random(2147483647); @@ -33,6 +36,12 @@ namespace pxsim { this.lightSensorState = new LightSensorState(); this.compassState = new CompassState(); this.neopixelState = new NeoPixelState(); + + // updates + this.updateSubscribers = [] + this.updateView = () => { + this.updateSubscribers.forEach(sub => sub()); + } } receiveMessage(msg: SimulatorMessage) { @@ -62,34 +71,20 @@ namespace pxsim { initAsync(msg: SimulatorRunMessage): Promise { let options = (msg.options || {}) as RuntimeOptions; - let boardDef = ARDUINO_ZERO; //TODO: read from pxt.json/pxttarget.json + //TODO: read from pxt.json/pxttarget.json + let boardDef = MICROBIT_DEF; + // let boardDef = ARDUINO_ZERO; + // let boardDef = SPARKFUN_PHOTON; + // let boardDef = RASPBERRYPI_MODELB; + let cmpsList = msg.parts; - cmpsList.sort(); let cmpDefs = COMPONENT_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json let fnArgs = msg.fnArgs; - let mb = true; - let view: visuals.GenericBoardSvg | visuals.MicrobitBoardSvg; - if (mb) { - view = new visuals.MicrobitBoardSvg({ - runtime: runtime, - theme: visuals.randomTheme(), - activeComponents: cmpsList, - fnArgs: fnArgs, - disableTilt: false - }); - } else { - view = new visuals.GenericBoardSvg({ - boardDef: boardDef, - activeComponents: cmpsList, - componentDefinitions: cmpDefs, - runtime: runtime, - fnArgs: fnArgs - }) - } + let viewHost = new visuals.BoardHost(this, boardDef, cmpsList, cmpDefs, fnArgs); document.body.innerHTML = ""; // clear children - document.body.appendChild(view.hostElement); + document.body.appendChild(viewHost.getView()); return Promise.resolve(); } diff --git a/sim/definitions.ts b/sim/definitions.ts index acddfe2f..9007215b 100644 --- a/sim/definitions.ts +++ b/sim/definitions.ts @@ -24,6 +24,7 @@ namespace pxsim { groundPins: string[], threeVoltPins: string[], attachPowerOnRight?: boolean, + onboardComponents?: string[] } export interface FactoryFunctionPinAlloc { type: "factoryfunction", @@ -92,6 +93,7 @@ namespace pxsim { groundPins: ["GND"], threeVoltPins: ["+3v3"], attachPowerOnRight: true, + onboardComponents: ["buttonpair", "ledmatrix"], } export const RASPBERRYPI_MODELB: BoardDefinition = { visual: { diff --git a/sim/microbit.ts b/sim/microbit.ts index 72552ce9..34174876 100644 --- a/sim/microbit.ts +++ b/sim/microbit.ts @@ -102,8 +102,7 @@ namespace pxsim.visuals { leave: "mouseleave" }; - export class MicrobitBoardSvg { - public hostElement: SVGSVGElement; + export class MicrobitBoardSvg implements BoardView { public element: SVGSVGElement; private style: SVGStyleElement; private defs: SVGDefsElement; @@ -132,131 +131,38 @@ namespace pxsim.visuals { private shakeButton: SVGCircleElement; private shakeText: SVGTextElement; public board: pxsim.DalBoard; - - //EXPERIMENTAl - private wireFactory: WireFactory; - private breadboard: Breadboard; - private components: IBoardComponent[] = []; private pinNmToCoord: Map = {}; - private fromBBCoord: (xy: Coord) => Coord; - private fromMBCoord: (xy: Coord) => Coord; constructor(public props: IBoardProps) { this.board = this.props.runtime.board as pxsim.DalBoard; - this.board.updateView = () => this.updateState(); + this.board.updateSubscribers.push(() => this.updateState()); - //EXPERIMENTAl - let boardDef = MICROBIT_DEF; - let cmpsDef: Map = COMPONENT_DEFINITIONS; - this.breadboard = new Breadboard(); - this.buildDom(); - this.hostElement = this.element; this.recordPinCoords(); - let cmps = props.activeComponents.filter(a => a === "neopixel"); - if (0 < cmps.length) { - let compRes = composeSVG({ - el1: {el: this.element, y: 0, x: 0, w: MB_WIDTH, h: MB_HEIGHT}, - scaleUnit1: littlePinDist * 1.7, - el2: this.breadboard.getSVGAndSize(), - scaleUnit2: this.breadboard.getPinDist(), - margin: [0, 0, 10, 0], - middleMargin: 80, - maxWidth: 299, - maxHeight: 433, - }); - let under = compRes.under; - let over = compRes.over; - this.hostElement = compRes.host; - let edges = compRes.edges; - this.fromMBCoord = compRes.toHostCoord1; - this.fromBBCoord = compRes.toHostCoord2; - let pinDist = compRes.scaleUnit; - - this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this)); - let allocRes = allocateDefinitions({ - boardDef: boardDef, - cmpDefs: cmpsDef, - fnArgs: this.props.fnArgs, - getBBCoord: this.getBBCoord.bind(this), - cmpList: cmps, - }); - this.addAll(allocRes); - } else { - svg.hydrate(this.hostElement, { - width: 299, - height: 433, - }); - } + this.buildDom(); this.updateTheme(); this.updateState(); this.attachEvents(); } - //EXPERIMENTAl - private getBoardPinCoord(pinNm: string): Coord { - let coord = this.pinNmToCoord[pinNm]; - return this.fromMBCoord(coord); + public getView(): SVGAndSize { + return { + el: this.element, + y: 0, + x: 0, + w: MB_WIDTH, + h: MB_HEIGHT + }; } - private getBBCoord(rowCol: BBRowCol): Coord { - let bbCoord = this.breadboard.getCoord(rowCol); - if (!bbCoord) - return null; - return this.fromBBCoord(bbCoord); + + public getCoord(pinNm: string): Coord { + return this.pinNmToCoord[pinNm]; } - public getLocCoord(loc: Loc): Coord { - let coord: Coord; - if (loc.type === "breadboard") { - let rowCol = (loc).rowCol; - coord = this.getBBCoord(rowCol); - } else { - let pinNm = (loc).pin; - coord = this.getBoardPinCoord(pinNm); - } - if (!coord) { - console.error("Unknown location: " + name) - return [0, 0]; - } - return coord; - } - public addWire(inst: WireInst): Wire { - return this.wireFactory.addWire(inst.start, inst.end, inst.color, true); - } - public addAll(basicWiresAndCmpsAndWires: AllocatorResult) { - let {powerWires, components} = basicWiresAndCmpsAndWires; - powerWires.forEach(w => this.addWire(w)); - components.forEach((cAndWs, idx) => { - let {component, wires} = cAndWs; - wires.forEach(w => this.addWire(w)); - this.addComponent(component); - }); - } - public addComponent(cmpDesc: CmpInst): IBoardComponent { - let cmp: IBoardComponent = null; - if (typeof cmpDesc.visual === "string") { - let builtinVisual = cmpDesc.visual as string; - let cnstr = builtinComponentSimVisual[builtinVisual]; - let stateFn = builtinComponentSimState[builtinVisual]; - let cmp = cnstr(); - cmp.init(this.board.bus, stateFn(this.board), this.element, cmpDesc.microbitPins, cmpDesc.otherArgs); - this.components.push(cmp); - this.hostElement.appendChild(cmp.element); - if (cmp.defs) - cmp.defs.forEach(d => this.defs.appendChild(d)); - this.style.textContent += cmp.style || ""; - let rowCol = [`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`]; - let coord = this.getBBCoord(rowCol); - cmp.moveToCoord(coord); - let getCmpClass = (type: string) => `sim-${type}-cmp`; - let cls = getCmpClass(name); - svg.addClass(cmp.element, cls); - svg.addClass(cmp.element, "sim-cmp"); - cmp.updateTheme(); - cmp.updateState(); - } else { - } - return cmp; + + public getPinDist(): number { + return littlePinDist * 1.7; } + public recordPinCoords() { const pinsY = 356.7 + 40; pinNames.forEach((nm, i) => { @@ -311,9 +217,6 @@ namespace pxsim.visuals { if (!runtime || runtime.dead) svg.addClass(this.element, "grayscale"); else svg.removeClass(this.element, "grayscale"); - - //EXPERIMENTAl - this.components.forEach(c => c.updateState()); } private updateGestures() { diff --git a/sim/simlib.ts b/sim/simlib.ts index e96bd947..7cc71230 100644 --- a/sim/simlib.ts +++ b/sim/simlib.ts @@ -202,4 +202,10 @@ namespace pxsim.visuals { export type SVGElAndSize = SVGAndSize; export const PIN_DIST = 15; + + export interface BoardView { + getView(): SVGAndSize; + getCoord(pinNm: string): Coord; + getPinDist(): number; + } } \ No newline at end of file diff --git a/sim/visuals/boardhost.ts b/sim/visuals/boardhost.ts new file mode 100644 index 00000000..b5eaee3f --- /dev/null +++ b/sim/visuals/boardhost.ts @@ -0,0 +1,160 @@ +namespace pxsim.visuals { + export class BoardHost { + private components: IBoardComponent[] = []; + private wireFactory: WireFactory; + private breadboard: Breadboard; + private fromBBCoord: (xy: Coord) => Coord; + private fromMBCoord: (xy: Coord) => Coord; + private boardView: BoardView; + private view: SVGSVGElement; + private style: SVGStyleElement; + private defs: SVGDefsElement; + private state: DalBoard; + + constructor(state: DalBoard, boardDef: BoardDefinition, cmpsList: string[], cmpDefs: Map, fnArgs: any) { + this.state = state; + let onboardCmps = boardDef.onboardComponents || []; + let activeComponents = cmpsList.filter(c => onboardCmps.indexOf(c) < 0); + activeComponents.sort(); + + if (boardDef.visual === "microbit") { + this.boardView = new visuals.MicrobitBoardSvg({ + runtime: runtime, + theme: visuals.randomTheme(), + activeComponents: activeComponents, + fnArgs: fnArgs, + disableTilt: false + }); + } else { + //TODO: port Arduino/generic board + // this.boardView = new visuals.GenericBoardSvg({ + // boardDef: boardDef, + // activeComponents: activeComponents, + // componentDefinitions: cmpDefs, + // runtime: runtime, + // fnArgs: fnArgs + // }) + } + + const VIEW_WIDTH = 299; + const VIEW_HEIGHT = 433; + + if (0 < activeComponents.length) { + this.breadboard = new Breadboard(); + let composition = composeSVG({ + el1: this.boardView.getView(), + scaleUnit1: this.boardView.getPinDist(), + el2: this.breadboard.getSVGAndSize(), + scaleUnit2: this.breadboard.getPinDist(), + margin: [0, 0, 10, 0], + middleMargin: 80, + maxWidth: VIEW_WIDTH, + maxHeight: VIEW_HEIGHT, + }); + let under = composition.under; + let over = composition.over; + this.view = composition.host; + let edges = composition.edges; + this.fromMBCoord = composition.toHostCoord1; + this.fromBBCoord = composition.toHostCoord2; + let pinDist = composition.scaleUnit; + + this.style = svg.child(this.view, "style", {}); + this.defs = svg.child(this.view, "defs", {}); + + this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this)); + + let allocRes = allocateDefinitions({ + boardDef: boardDef, + cmpDefs: cmpDefs, + fnArgs: fnArgs, + getBBCoord: this.breadboard.getCoord.bind(this.breadboard), + cmpList: activeComponents, + }); + + this.addAll(allocRes); + } else { + let el = this.boardView.getView().el; + this.view = el; + svg.hydrate(this.view, { + width: VIEW_WIDTH, + height: VIEW_HEIGHT, + }); + } + + this.state.updateSubscribers.push(() => this.updateState()); + } + + public getView(): SVGElement { + return this.view; + } + + private updateState() { + this.components.forEach(c => c.updateState()); + } + + private getBBCoord(rowCol: BBRowCol) { + let bbCoord = this.breadboard.getCoord(rowCol); + return this.fromBBCoord(bbCoord); + } + private getPinCoord(pin: string) { + let boardCoord = this.boardView.getCoord(pin); + return this.fromMBCoord(boardCoord); + } + public getLocCoord(loc: Loc): Coord { + let coord: Coord; + if (loc.type === "breadboard") { + let rowCol = (loc).rowCol; + coord = this.getBBCoord(rowCol); + } else { + let pinNm = (loc).pin; + coord = this.getPinCoord(pinNm); + } + if (!coord) { + console.error("Unknown location: " + name) + return [0, 0]; + } + return coord; + } + + public addComponent(cmpDesc: CmpInst): IBoardComponent { + let cmp: IBoardComponent = null; + if (typeof cmpDesc.visual === "string") { + let builtinVisual = cmpDesc.visual as string; + let cnstr = builtinComponentSimVisual[builtinVisual]; + let stateFn = builtinComponentSimState[builtinVisual]; + let cmp = cnstr(); + cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs); + this.components.push(cmp); + this.view.appendChild(cmp.element); + if (cmp.defs) + cmp.defs.forEach(d => this.defs.appendChild(d)); + this.style.textContent += cmp.style || ""; + let rowCol = [`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`]; + let coord = this.getBBCoord(rowCol); + cmp.moveToCoord(coord); + let getCmpClass = (type: string) => `sim-${type}-cmp`; + let cls = getCmpClass(name); + svg.addClass(cmp.element, cls); + svg.addClass(cmp.element, "sim-cmp"); + cmp.updateTheme(); + cmp.updateState(); + } else { + //TODO: support generic parts + } + return cmp; + } + public addWire(inst: WireInst): Wire { + return this.wireFactory.addWire(inst.start, inst.end, inst.color, true); + } + public addAll(basicWiresAndCmpsAndWires: AllocatorResult) { + let {powerWires, components} = basicWiresAndCmpsAndWires; + powerWires.forEach(w => this.addWire(w)); + components.forEach((cAndWs, idx) => { + let {component, wires} = cAndWs; + wires.forEach(w => this.addWire(w)); + this.addComponent(component); + }); + } + } +} \ No newline at end of file diff --git a/sim/visuals/genericboard.ts b/sim/visuals/genericboard.ts index 76ca57b4..3023bbec 100644 --- a/sim/visuals/genericboard.ts +++ b/sim/visuals/genericboard.ts @@ -180,7 +180,7 @@ namespace pxsim.visuals { `; let nextBoardId = 0; - export class GenericBoardSvg { + export class GenericBoardSvg /*TODO: implements BoardView*/ { public hostElement: SVGSVGElement; private style: SVGStyleElement; private defs: SVGDefsElement;