Skip to content

Commit

Permalink
Merge pull request #2235 from concord-consortium/187091498-dataflow-w…
Browse files Browse the repository at this point in the history
…ait-2

187091498 dataflow wait 2
  • Loading branch information
scytacki authored Mar 26, 2024
2 parents a96e7f5 + 764d7e7 commit d56ecc0
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 51 deletions.
4 changes: 2 additions & 2 deletions cypress/e2e/functional/tile_tests/dataflow_tool_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,11 @@ context('Dataflow Tool Tile', function () {

cy.log("verify control operator types");
const controlOperator = "controlOperator";
const controlOperatorTypes = ["Hold this", "Hold previous", "Hold 0"];
const controlOperatorDisplayNames = ["Hold this", "Hold previous", "Hold 0"];
dataflowToolTile.getDropdown(controlNode, controlOperator).click();
dataflowToolTile.getDropdownOptions(controlNode, controlOperator).should("have.length", 3);
dataflowToolTile.getDropdownOptions(controlNode, controlOperator).each(($tab, index, $typeList) => {
expect($tab.text()).to.contain(controlOperatorTypes[index]);
expect($tab.text()).to.contain(controlOperatorDisplayNames[index]);
});
dataflowToolTile.getDropdownOptions(controlNode, controlOperator).last().click();
dataflowToolTile.getDropdownOptions(controlNode, controlOperator).should("have.length", 0);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/dataflow/model/dataflow-program-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const DataflowNodeDataModel = types.

// Control
controlOperator: types.maybe(types.string),
waitDuration: types.maybe(types.number),

// Demo Output
outputType: types.maybe(types.string),
Expand Down
20 changes: 19 additions & 1 deletion src/plugins/dataflow/model/utilities/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,25 @@ export const HoldFunctionOptions = [
displayName: "Hold 0",
type: "control",
icon: HoldZeroArrowIcon
}
},
// {
// name: "Hold Current Wait",
// displayName: "Hold this, wait",
// type: "control",
// icon: HoldCurrentArrowIcon
// },
// {
// name: "Hold Prior Wait",
// displayName: "Hold previous, wait",
// type: "control",
// icon: HoldPreviousArrowIcon
// },
// {
// name: "Output Zero Wait",
// displayName: "Hold 0, wait",
// type: "control",
// icon: HoldZeroArrowIcon
// }
];

export const NodeSensorTypes = [
Expand Down
9 changes: 8 additions & 1 deletion src/plugins/dataflow/nodes/controls/num-control.sass
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

.number-input
border-radius: 3px
&.units
&.units.multiple
border-top-right-radius: 0
border-bottom-right-radius: 0

Expand Down Expand Up @@ -48,3 +48,10 @@
margin: 0
box-sizing: border-box
box-shadow: none

.number-container.units.one
width: 97%

.single-unit
text-align: center
padding-left: 5px
53 changes: 36 additions & 17 deletions src/plugins/dataflow/nodes/controls/num-control.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// FIXME: ESLint is unhappy with these control components
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useRef } from "react";
import classNames from "classnames";
import Rete, { NodeEditor, Node, Control } from "rete";
import { useStopEventPropagation } from "./custom-hooks";
import { NodePeriodUnits } from "../../model/utilities/node";
Expand Down Expand Up @@ -59,35 +60,53 @@ export class NumControl extends Rete.Control {
tooltip: string}) => {
const inputRef = useRef<HTMLInputElement>(null);
useStopEventPropagation(inputRef, "pointerdown");

const unitsClasses = classNames({
"units one": compProps.units && compProps.units.length === 1,
"units multiple": compProps.units && compProps.units.length > 1
});

const renderUnits = () => {
if (!compProps.units || compProps.units.length === 0) {
return null;
}

if (compProps.units.length === 1) {
return (
<div className="single-unit">
{compProps.units[0]}
</div>
);
}

return (
<div className="type-options-back">
<div className="type-options">
<select onChange={handleSelectChange} value={compProps.currentUnits}>
{compProps.units.map((unit, index) => (
<option key={index} value={unit}>{unit}</option>
))}
</select>
</div>
</div>
);
};

return (
<div className="number-container" title={compProps.tooltip}>
<div className={`number-container ${unitsClasses}`} title={compProps.tooltip}>
{ label
? <label className="number-label">{compProps.label}</label>
: null
}
<input className={`number-input ${compProps.units && compProps.units.length ? "units" : ""}`}
<input className={`number-input ${unitsClasses}`}
ref={inputRef}
type={"text"}
value={compProps.inputValue}
onKeyPress={handleKeyPress}
onChange={handleChange(compProps.onChange)}
onBlur={handleBlur(compProps.onBlur)}
/>
{ compProps.units?.length
? <div className="type-options-back">
<div className="type-options">
<select onChange={handleSelectChange}
value={compProps.currentUnits}
>
{ compProps.units.map((unit, index) => (
<option key={index} value={unit}>{unit}</option>
))
}
</select>
</div>
</div>
: null
}
{renderUnits()}
</div>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/dataflow/nodes/dataflow-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export class DataflowNode extends Node {

const dynamicClasses = classNames({
"gate-active": node.data.gateActive,
"wait-option-selected": node.data.hasWait,
"wait-active": node.data.waitActive,
"has-flow-in": hasFlowIn(node),
"uses-relays": outputsToAnyRelay(node),
"uses-gripper": outputsToAnyGripper(node),
Expand Down
72 changes: 43 additions & 29 deletions src/plugins/dataflow/nodes/factories/control-rete-node-factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ import { NodeData } from "rete/types/core/data";
import { DataflowReteNodeFactory } from "./dataflow-rete-node-factory";
import { ValueControl } from "../controls/value-control";
import { DropdownListControl } from "../controls/dropdown-list-control";
import { NumControl } from "../controls/num-control";
import { HoldFunctionOptions } from "../../model/utilities/node";
import { PlotButtonControl } from "../controls/plot-button-control";
import { getNumDisplayStr } from "../utilities/view-utilities";
import { determineGateAndTimerStates, getHoldNodeResultString } from "../utilities/view-utilities";

interface HoldNodeData {
gateActive: boolean;
heldValue: number | null;
timerRunning: boolean;
}
export class ControlReteNodeFactory extends DataflowReteNodeFactory {
constructor(numSocket: Socket) {
super("Control", numSocket);
}

private heldValue: number | null = null;
private startTimer(node: NodeData, duration: number) {
if (node.data.timerRunning) return;
node.data.timerRunning = true;
setTimeout(() => {
node.data.timerRunning = false;
}, duration * 1000);
}

public builder(node: Node) {
super.defaultBuilder(node);
Expand All @@ -21,7 +33,12 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory {
const valueInput = new Rete.Input("num2", "Number2", this.numSocket);
const out = new Rete.Output("num", "Number", this.numSocket);

node.data.gateActive = false;
node.data = {
...node.data,
gateActive: false,
heldValue: null,
timerRunning: false,
} as NodeData['data'] & HoldNodeData;

const dropdownOptions = HoldFunctionOptions
.map((nodeOp) => {
Expand All @@ -31,75 +48,72 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory {
.addInput(valueInput)
.addInput(binaryInput)
.addControl(new DropdownListControl(this.editor, "controlOperator", node, dropdownOptions, true))
.addControl(new NumControl(this.editor, "waitDuration", node, true, "wait", 0, 0, ["sec"], "wait"))
.addControl(new PlotButtonControl(this.editor, "plot", node))
.addControl(new ValueControl(this.editor, "nodeValue", node))
.addOutput(out) as any;
}
}

public worker(node: NodeData, inputs: any, outputs: any) {
const signalValue :number = inputs.num2 ? (inputs.num2.length ? inputs.num2[0] : node.data.num2) : 0;
const funcName = node.data.controlOperator as string;
const recents: number[] | undefined = (node.data.recentValues as any)?.nodeValue;
const lastRecentValue = recents?.[recents.length - 1];
const priorValue = lastRecentValue == null ? null : lastRecentValue;
const n1 :number = inputs.num1.length ? inputs.num1[0] : node.data.num1;
const n2 :number = inputs.num2 ? (inputs.num2.length ? inputs.num2[0] : node.data.num2) : 0;
const isTimerRunning = node.data.timerRunning as boolean;

let result = 0;
let cResult = 0;

// for setting classes on node
node.data.gateActive = n1 === 1;
const { activateGate, startTimer } = determineGateAndTimerStates(node, inputs, isTimerRunning);
startTimer && this.startTimer(node, node.data.waitDuration as number);
node.data.gateActive = activateGate;

// requires value in n2 (except for case of Output Zero)
if (isNaN(n2)) {
this.heldValue = null;
// requires value in signalValue (except for case of Output Zero)
if (isNaN(signalValue)) {
node.data.heldValue = null;
result = NaN;
cResult = NaN;
}

// For each function, evaluate given inputs and node state
// TODO - check and see if this gets serialized, and if so, how to handle legacy funcNames on load
if (funcName === "Hold 0" || funcName === "Output Zero"){
this.heldValue = null;
result = node.data.gateActive ? 0 : n2;
node.data.heldValue = null;
result = node.data.gateActive ? 0 : signalValue;
cResult = 0;
}

else if (funcName === "Hold Current"){
if (node.data.gateActive){
// Already a number here? Maintain. Otherwise set the new held value;
this.heldValue = typeof this.heldValue === "number" ? this.heldValue : n2;
result = this.heldValue;
cResult = this.heldValue;
node.data.heldValue = typeof node.data.heldValue === "number" ? node.data.heldValue : signalValue;
result = node.data.heldValue as number;
cResult = node.data.heldValue as number;
}
else {
this.heldValue = null;
result = n2;
cResult = n2; // still n2, since the value to be held would be the current
node.data.heldValue = null;
result = signalValue;
cResult = signalValue; // still signalValue, since the value to be held would be the current
}
}

else if (funcName === "Hold Prior"){
if (node.data.gateActive){
// Already a number here? Maintain. Otherwise set the new held value;
this.heldValue = typeof this.heldValue === "number" ? this.heldValue : priorValue;
result = this.heldValue || 0;
cResult = this.heldValue || 0;
node.data.heldValue = typeof node.data.heldValue === "number" ? node.data.heldValue : priorValue;
result = node.data.heldValue as number || 0;
cResult = node.data.heldValue as number || 0;
}
else {
this.heldValue = null;
result = n2;
node.data.heldValue = null;
result = signalValue;
cResult = priorValue || 0;
}
}

// prepare string to display on node
const resultString = getNumDisplayStr(result);
const cResultString = getNumDisplayStr(cResult);
const onString = `on → ${cResultString}`;
const offString = `off → ${resultString}`;
const resultSentence = node.data.gateActive ? onString : offString;
const resultSentence = getHoldNodeResultString(node, result, cResult, isTimerRunning) || "";

// operate rete
if (this.editor) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dataflow/nodes/node-states.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
.number2 .socket::before {
content: "";
position: absolute;
top: 63px;
top: 93px;
left: -14px;
width: 20px;
height: 20px;
Expand Down
19 changes: 19 additions & 0 deletions src/plugins/dataflow/nodes/utilities/view-utilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NodeEditor, Node, Input } from "rete";
import { Rect, scaleRect, unionRect } from "../../utilities/rect";
import { kEmptyValueString } from "../factories/dataflow-rete-node-factory";
import { NodeData } from "rete/types/core/data";

function getBoundingRectOfNode(n: Node, editor: NodeEditor): Rect | undefined {
const { k } = editor.view.area.transform;
Expand Down Expand Up @@ -75,3 +76,21 @@ export function getInsertionOrder(editor: NodeEditor, id: number) {
return index + 1;
}

export function getHoldNodeResultString(node: NodeData, result: number, calcResult: number, timerRunning: boolean){
const resultString = getNumDisplayStr(result);
const cResultString = getNumDisplayStr(calcResult);
const waitString = `waiting → ${cResultString}`;
const onString = `on → ${cResultString}`;
const offString = `off → ${resultString}`;

if (node.data.gateActive) return timerRunning ? waitString : onString;
else return offString;
}

export function determineGateAndTimerStates(node: NodeData, inputs: any, timerRunning: boolean){
const timerIsOption = node.data.waitDuration as number > 0;
const switchIn = inputs.num1[0];
const startTimer = timerIsOption && switchIn === 1 && !timerRunning;
const activateGate = timerRunning ? true : switchIn === 1;
return { activateGate, startTimer };
}

0 comments on commit d56ecc0

Please sign in to comment.